├── .DS_Store ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── app ├── .DS_Store ├── css │ └── base_style_sb.css ├── html │ └── sidebar.html ├── img │ └── home.svg ├── js │ ├── fts_fuzzy_match.js │ ├── functions │ │ ├── sb_fn.js │ │ ├── settings.js │ │ └── utils.js │ └── sidebar_node.js ├── panels │ ├── .DS_Store │ ├── assets_downloader │ │ ├── assets_downloader.html │ │ ├── assets_downloader.jsb │ │ └── style.css │ ├── custom_categories │ │ ├── custom_categories.html │ │ ├── custom_categories.jsb │ │ └── style.css │ ├── custom_templates │ │ ├── custom_templates.html │ │ ├── custom_templates.jsb │ │ └── style.css │ ├── custom_workflows │ │ ├── custom_workflows.html │ │ ├── custom_workflows.jsb │ │ └── style.css │ └── terminal │ │ ├── style.css │ │ ├── terminal.html │ │ └── terminal.jsb └── views │ ├── custom_views.json │ ├── views.json │ └── views_default.json ├── images ├── .DS_Store ├── YouTube.svg ├── assets.gif ├── custom_categories.gif ├── dd.gif ├── expand_collapse.gif ├── fuzzysearch.gif ├── header.png ├── patreon_badge.png ├── pin.gif ├── pin_reorder.gif ├── preview.gif ├── search_categories.gif ├── search_nodes.gif ├── settings.png ├── templates.gif ├── theme.gif └── workflows.gif ├── pyproject.toml └── requirements.txt /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/.DS_Store -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Prerequisites** 11 | 12 | Before submitting a bug report, please ensure you have followed these steps: 13 | 14 | 1. **Browser Verification:** 15 | - Check if the issue occurs in a different browser. 16 | - Alternatively, try to reproduce the issue in incognito mode (this helps rule out cache-related issues). 17 | - If the issue is related to cache, clear the cache for the ComfyUI address (usually 127.0.0.1:8188 if it's running in local) in your browser settings or try with force Reload: Ctrl + F5 on Windows or Cmd + Shift + R on macOS 18 | 19 | 2. **Sidebar Test:** 20 | - Remove additional nodes by doing the following: 21 | - Navigate to the `custom_nodes` folder. 22 | - Cut all nodes and paste them into a temporary folder outside of `custom_nodes`, except for `ComfyUI-N-Sidebar` and, if present, `ComfyUI-Manager`**. 23 | - Launch ComfyUI and test if the problem persists. 24 | 25 | ** you can restore the original situation by moving back the nodes from the temporary folder to the `custom_nodes` folder and restarting ComfyUI. 26 | 27 | 3. **Identify Node Conflicts:** 28 | - If the problem is resolved, it is likely caused by a conflict with one of the removed nodes. 29 | - Try to identify the conflicting node: maybe it was added recently if the problem did not exist before. 30 | - If you find the conflicting node, please open an issue to inform me so I can attempt to resolve the problem (if possible). 31 | 32 | **Bug Report** 33 | 34 | If the problem persists after following the above steps, please provide the following information: 35 | 36 | - **Description:** 37 | - Provide a brief description of the bug. 38 | 39 | - **Steps to Reproduce:** 40 | - List the steps you took to encounter the issue. 41 | 42 | - **Expected Behavior:** 43 | - Describe what you expected to happen. 44 | 45 | - **Actual Behavior:** 46 | - Describe what actually happened. 47 | 48 | - **System Information:** 49 | - **Operating System:** 50 | - **Browser:** 51 | 52 | **Additional Context** 53 | 54 | Provide any additional information that may help in resolving the issue. 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature request] " 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.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 | permissions: 11 | issues: write 12 | 13 | jobs: 14 | publish-node: 15 | name: Publish Custom Node to registry 16 | runs-on: ubuntu-latest 17 | if: ${{ github.repository_owner == 'Nuked88' }} 18 | steps: 19 | - name: Check out code 20 | uses: actions/checkout@v4 21 | - name: Publish Custom Node 22 | uses: Comfy-Org/publish-node-action@v1 23 | with: 24 | ## Add your own personal access token to your Github Repository secrets and reference it here. 25 | personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Config file 2 | app/settings.json 3 | app/settings.json.bak 4 | app/views/views_backup.json 5 | metadata/* 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # vscode 12 | *.code-workspace 13 | .vscode/* 14 | 15 | # C extensions 16 | *.so 17 | 18 | # Distribution / packaging 19 | .Python 20 | build/ 21 | develop-eggs/ 22 | dist/ 23 | downloads/ 24 | eggs/ 25 | .eggs/ 26 | lib/ 27 | lib64/ 28 | parts/ 29 | sdist/ 30 | var/ 31 | wheels/ 32 | share/python-wheels/ 33 | *.egg-info/ 34 | .installed.cfg 35 | *.egg 36 | MANIFEST 37 | 38 | # PyInstaller 39 | # Usually these files are written by a python script from a template 40 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 41 | *.manifest 42 | *.spec 43 | 44 | # Installer logs 45 | pip-log.txt 46 | pip-delete-this-directory.txt 47 | 48 | # Unit test / coverage reports 49 | htmlcov/ 50 | .tox/ 51 | .nox/ 52 | .coverage 53 | .coverage.* 54 | .cache 55 | nosetests.xml 56 | coverage.xml 57 | *.cover 58 | *.py,cover 59 | .hypothesis/ 60 | .pytest_cache/ 61 | cover/ 62 | 63 | # Translations 64 | *.mo 65 | *.pot 66 | 67 | # Django stuff: 68 | *.log 69 | local_settings.py 70 | db.sqlite3 71 | db.sqlite3-journal 72 | 73 | # Flask stuff: 74 | instance/ 75 | .webassets-cache 76 | 77 | # Scrapy stuff: 78 | .scrapy 79 | 80 | # Sphinx documentation 81 | docs/_build/ 82 | 83 | # PyBuilder 84 | .pybuilder/ 85 | target/ 86 | 87 | # Jupyter Notebook 88 | .ipynb_checkpoints 89 | 90 | # IPython 91 | profile_default/ 92 | ipython_config.py 93 | 94 | # pyenv 95 | # For a library or package, you might want to ignore these files since the code is 96 | # intended to run in multiple environments; otherwise, check them in: 97 | # .python-version 98 | 99 | # pipenv 100 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 101 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 102 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 103 | # install all needed dependencies. 104 | #Pipfile.lock 105 | 106 | # poetry 107 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 108 | # This is especially recommended for binary packages to ensure reproducibility, and is more 109 | # commonly ignored for libraries. 110 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 111 | #poetry.lock 112 | 113 | # pdm 114 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 115 | #pdm.lock 116 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 117 | # in version control. 118 | # https://pdm.fming.dev/#use-with-ide 119 | .pdm.toml 120 | 121 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 122 | __pypackages__/ 123 | 124 | # Celery stuff 125 | celerybeat-schedule 126 | celerybeat.pid 127 | 128 | # SageMath parsed files 129 | *.sage.py 130 | 131 | # Environments 132 | .env 133 | .venv 134 | env/ 135 | venv/ 136 | ENV/ 137 | env.bak/ 138 | venv.bak/ 139 | 140 | # Spyder project settings 141 | .spyderproject 142 | .spyproject 143 | 144 | # Rope project settings 145 | .ropeproject 146 | 147 | # mkdocs documentation 148 | /site 149 | 150 | # mypy 151 | .mypy_cache/ 152 | .dmypy.json 153 | dmypy.json 154 | 155 | # Pyre type checker 156 | .pyre/ 157 | 158 | # pytype static type analyzer 159 | .pytype/ 160 | 161 | # Cython debug symbols 162 | cython_debug/ 163 | 164 | # PyCharm 165 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 166 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 167 | # and can be added to the global gitignore or merged into this file. For a more nuclear 168 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 169 | #.idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 pythongosssss 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![Drag and Drop Nodes](./images/header.png) 3 | 4 | 5 | 6 | [![YouTube](./images/YouTube.svg)](https://www.youtube.com/channel/UCnu819ZX2xiusPpbQ4KzSmA) 7 | 8 | # ComfyUI-N-Sidebar 9 | A simple sidebar for ComfyUI. 10 | For what i know nobody did it, so i did it. 11 | Maybe you don't need it. I need it >.< 12 | 13 | 14 | # Updates 15 | 16 | - 08-09-2024 17 | - Custom shortcuts! 18 | 19 | - 04-09-2024 20 | - Changed the resize system of the n-sidebar (it can now be resized directly from its sides NOTE: the top side wilL only move the bar) 21 | - Added the ability to directly download CIVITAI models into chosen folders (this includes Checkpoint, Textual Inversion, Hypernetwork, Aesthetic Gradient, LORA, LoCon, DoRA, Controlnet, Upscaler, Motion Module, VAE, Poses, Wildcards, Workflows). [More Info](#assets-downloader). 22 | - Added the ability to import/export your settings. 23 | - EXPERIMENTAL: You can now embed ComfyUI's native bar within the n-sidebar 24 | - EXPERIMENTAL: Added a small console (useful if you don’t have direct access to the machine). If you want to remove this feature, simply delete the `terminal` folder within `ComfyUI-N-Sidebar/app/panels` 25 | - EXPERIMENTAL: Added the ability to edit the layout of the bar, this functionality is highly unstable and its use may lead to unexpected behaviors 26 | - Redesigned configuration menu 27 | - Fixed some bugs 28 | 29 | - 01-08-2024 30 | - A lot of people are having trouble with the CSS class change in the last update because of a browser cache issue: the old CSS file keeps loading from disk instead of the new one with the latest changes (thanks, modern browsers!). To fix this, I've changed the name of the CSS file, which SHOULD force browsers to load the new one. If this doesn't fix the issue, I'm not sure what will 😭. 31 | - Added the new option `Show at Startup` in the settings. This lets you have the sidebar open at startup (if it's not in fixed mode). 32 | - Changed the refresh icon so that it displays correctly in Chromium browsers on macOS. 33 | - Minor change to the CSS 34 | 35 | - 30-07-2024 36 | - Changed the name of the sidebar class to prevent a conflict with the `comfyui-search-navigation` node. 37 | 38 |
39 | Previous Updates 40 | 41 | - 21-07-2024 42 | - Fixed a cache bug on the templates panel 43 | 44 | - 19-07-2024 45 | - Added OSX shortcut support 46 | 47 | - 05-07-2024 48 | - Fixed bug on dragging templates 49 | - Added multi user support on templates 50 | 51 | 52 | - 04-06-2024 53 | - Fixed some bugs 54 | - Changed icon bar positioning to the border of the screen 55 | - Implemented the DAMN subcategory view on the node panel!!! xD (you can disable it in the settings if you don't want it) 56 | - Added the ability to disable the "Auto Show" feature 57 | - Added the ability to view folders inside the workflow panel 58 | - Added some tooltips for better UX 59 | - Changed the Custom Node Categories icon (I didn't like the old one) 60 | - Added a little icon in custom categories and folders/subcategories 61 | - Added the ability to move workflows in custom categories via the context menu 62 | - Added the ability to open workflows in another TAB 63 | 64 | NOTE: If you had some custom categories in the workflow panel, at the first start they will be migrated to the new system (since I did a full rewrite to support the folders). It is possible that you need to refresh the page more than once to see them again. Also, a backup of the settings.json will be created before the migration in case anything goes wrong. 65 | 66 | NOTE2: I hope I did not break anything T_T 67 | 68 | 69 | - 25-05-2024 70 | - Fixed some bugs 71 | - Added Templates support** 72 | - Added ability categorize custom categories 73 | - Added preview (list of used nodes) 74 | 75 | - Added Workflow support! 76 | - Added ability to rename workflows 77 | - Added ability to remove workflows 78 | - Added ability to categorize workflows 79 | - Added preview (list of used nodes), (disabled by default) 80 | 81 | ** i did not enabled the **rename** and **delete** because it's conflicing with the TemplateManager 82 | 83 | 84 | - 21-04-2024 85 | - Rewritten a lot of the code to improve performance and readability, and slightly adjusted the UI to ensure compatibility with future updates. 86 | - ADDED CUSTOM CATEGORIES 87 | - Added custom setting panel 88 | - Re-Implemented the 'original' search function to support translated versions of ComfyUI (requires testing) 89 | - Added LEFT and RIGHT positioning options 90 | - Added "Auto Hide" feature 91 | - Now you have the option to use the default node ordering in ComfyUI instead of the alphabetical one 92 | - Added the possibility to perform a Soft or Factory Reset of the configuration 93 | - Now most configuration settings are stored in the settings.json file. 94 | - Fixed some bugs 95 | - Added some other bugs for sure! 96 | 97 | - 31-03-2024 98 | - Added fuzzy search (work in reverse too!) (enabled by default) - You can disable it in the settings 99 | - Added description of the node under the preview (Visible only if the description is set in the node) 100 | - Added possibility to collapse pinned node/custom nodes sections 101 | - Added possibility to set the bottom space for the sidebar 102 | - Added the scroll to top button 103 | - Fixed some bugs 104 | - Started migration from cookies to localstorage 105 | - 🐰 Happy Easter! 🐰 106 |
107 | 108 | # Installation 109 | 110 | 1. Clone the repository: 111 | `git clone https://github.com/Nuked88/ComfyUI-N-Sidebar.git` 112 | to your ComfyUI `custom_nodes` directory 113 | 2. Enjoy! 114 | 115 | NOTE: If you choose to use a different method to install the ComfyUI-N-Sidebar, please ensure that you rename the folder to `ComfyUI-N-Sidebar`. 116 | 117 | # Uninstall 118 | - Delete the `ComfyUI-N-Sidebar` folder in `custom_nodes` 119 | 120 | 121 | # Update 122 | 1. Navigate to the cloned repo e.g. `custom_nodes/ComfyUI-N-Sidebar` 123 | 2. `git pull` 124 | 125 | 126 | # Settings 127 | The most important settings are stored in `custom_nodes/ComfyUI-N-Sidebar/settings.json` 128 | 129 | 130 | # Keyboard Shortcuts 131 | 132 | - `Alt+Z` or `Option+Z` to toggle show/hide sidebar 133 | - `Alt+X` or `Option+X` to focus on the search field 134 | - `Alt+G` or `Option+G` to open the settings 135 | 136 | # How To Set Workflows Folder 137 | 138 | 1. Open the settings with the ⚙ icon or use the shortcut `Alt+G` (`Option+G` on Mac). 139 | 2. In **WORKFLOW PATHS**, set the folder path to where your workflows are located. If you want to set multiple paths, just put one folder path per line! 140 | 3. Restart ComfyUI 141 | 142 | # Features 143 | 144 | ### 🖱️Drag and Drop Nodes🖱️ 145 | ![Drag and Drop Nodes](./images/dd.gif) 146 | 147 | 148 | ### 📌Pin Your Favorite Node📌 149 | ![Pin Your Favorite Node](./images/pin.gif) 150 | 151 | ### 🔍Search within your nodes📄 152 | ![Search within your nodes](./images/search_nodes.gif) 153 | 154 | ### 🔍Search within categories📂 155 | ![Search within categories](./images/search_categories.gif) 156 | 157 | ### 🔍Fuzzy Search🔄 158 | ![Fuzzy Search](./images/fuzzysearch.gif) 159 | This feature is enabled by default, you can disable it in the settings. I've used fts_fuzzy_match.js by [Forrest Smith](https://github.com/forrestthewoods/lib_fts) 160 | 161 | ### 🎨 Custom Categories 📂 162 | ![Custom Categories](./images/custom_categories.gif) 163 | 164 | 165 | ### ➕Expand/Collapse Categories/Sidebar➖ 166 | ![Expand/Collapse Categories and Sidebar](./images/expand_collapse.gif) 167 | 168 | ### 🔁Reorder Nodes🔁 169 | ![Reorder Nodes](./images/pin_reorder.gif) 170 |
171 | **Note**: The node will be placed **before** the element on which it is dragged! 172 | 173 | ### 👁 Preview Node 👁 174 | ![Preview Node](./images/preview.gif) 175 | 176 | ### 📄 Templates Support 📄 177 | ![Templates Support](./images/templates.gif) 178 | 179 | #### 📄 Workflow Support 📄 180 | ![Workflow Support](./images/workflows.gif) 181 | 182 | ### 🎨 ComfyUI Themes Support 🎨 183 | ![Themes Support ](./images/theme.gif) 184 | 185 | ### 🎨 New Settings Panel 🎨 186 | ![ComfyUI Settings](./images/settings.png) 187 | 188 | ### ASSETS DOWNLOADER 189 | ![ComfyUI Assets](./images/assets.gif) 190 | 191 | This new panel allows you to download CIVITAI models (Checkpoint, TextualInversion, Hypernetwork, AestheticGradient, LORA, LoCon, DoRA, Controlnet, Upscaler, MotionModule, VAE, Poses, Wildcards, Workflows). 192 | The various models will be downloaded into the folders set in the settings. 193 | There is also the option to rebuild the "My Library" section (the dots at the top right corner): 194 | once the reference folders are set in the settings, by launching the rebuild, the already existing models will be identified and added to the library (if they exist in the CivitAI database). 195 | 196 | NOTE: you can set your own API key for models that are not publicly available. 197 | 198 | 199 | 200 | 201 | 202 | ### Todo: 203 | - [x] Reordering pinned nodes 204 | - [x] Node preview (i don't think it will be an image) 205 | - [x] Color integration with Jovimetrix 206 | - [x] Better search 207 | - [x] Custom Categories!! 208 | - [x] Workflows 209 | - [x] Templates 210 | - [x] Export and Import Settings 211 | - [x] Custom Shortcuts 212 | - [ ] Touch Support 213 | 214 | 215 | ### Known Issues: 216 | - After you drag a template onto the workflow, if you drag any other file from your PC that does not include a workflow, it will paste the last template you dragged again. 217 | 218 | ## Contributing 219 | 220 | Feel free to contribute to this project by reporting issues or suggesting improvements. Open an issue or submit a pull request on the GitHub repository. 221 | 222 | ## Donations 223 | 224 | If you'd like to support the project, consider making a donation ❤️ 225 |
226 | [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/C0C0AJECJ) 227 | Support me on Patreon 228 | 229 | ## License 230 | 231 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 232 | 233 | -------------------------------------------------------------------------------- /app/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/app/.DS_Store -------------------------------------------------------------------------------- /app/css/base_style_sb.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | .litegraph .dialog { 5 | z-index: 100 !important; 6 | } 7 | 8 | :root { 9 | --n-sidebar-width: 30px; 10 | --sidebar-menu-item-width: 24px; 11 | --main-menubar-height:35px; 12 | } 13 | .sidebar { 14 | 15 | position: absolute; 16 | top: 0; 17 | left: -250px; 18 | left: -250px; 19 | width: fit-content; 20 | height: calc(100% - 19px); 21 | color: var(--drag-text); 22 | transition: left 0.3s ease; 23 | z-index: 2; 24 | padding-top: 19px; 25 | left: 0; 26 | user-select: none; 27 | box-sizing: content-box; 28 | 29 | 30 | display: flex; 31 | flex-direction: row; 32 | justify-content: space-around; 33 | align-items: flex-start; 34 | } 35 | 36 | 37 | sidebar * { 38 | box-sizing: revert; 39 | } 40 | 41 | .sidebar ul { 42 | list-style-type: none; 43 | /* border-bottom: 6px solid rgb(from var(--comfy-input-bg) r g b / 60%);*/ 44 | 45 | padding-left: 5px; 46 | padding-right: 5px; 47 | /*margin-bottom: 8px;*/ 48 | margin-top: 0; 49 | padding-top: 5px; 50 | width: 100%; 51 | } 52 | 53 | .sidebar li { 54 | padding: 10px; 55 | cursor: pointer; 56 | user-select: none; 57 | color: var(--input-text); 58 | margin-left: 4px; 59 | } 60 | 61 | .content_sidebar { 62 | 63 | background: rgb(from var(--comfy-menu-bg) r g b / 100%); 64 | overflow-y: scroll; 65 | overflow-x: hidden; 66 | height: 100%; 67 | float: left; 68 | backdrop-filter: blur(5px); 69 | width: calc(100% - 45px); 70 | 71 | 72 | 73 | 74 | 75 | } 76 | 77 | .dragHandle { 78 | position: relative; 79 | float: left; 80 | right: 5; 81 | top: 0; 82 | height: 100%; 83 | width: 5px; 84 | cursor: ew-resize; 85 | background: rgb(119 111 111 / 0%); 86 | /*background: linear-gradient(90deg, rgb(62 62 62 / 46%) 0%, rgb(39 39 39 / 47%) 50%, rgb(28 28 28 / 31%) 100%);*/ 87 | } 88 | 89 | .dragHandleTB { 90 | background: #ffffff00; 91 | height: 10px; 92 | width: calc(100% - 10px); 93 | position: absolute; 94 | cursor: n-resize; 95 | 96 | } 97 | 98 | #dragHandleV { 99 | bottom: -5px; 100 | } 101 | 102 | #dragHandleT { 103 | 104 | top: 15px; 105 | z-index: 999999; 106 | } 107 | #searchInputSB { 108 | box-sizing: border-box; 109 | width: 100%; 110 | border-radius: 5px; 111 | padding: 10px; 112 | border: none; 113 | user-select: none; 114 | background: var(--comfy-input-bg); 115 | color: var(--input-text); 116 | line-height: 1.4; 117 | border: 1px solid var(--border-color); 118 | } 119 | 120 | .sidebar-header { 121 | position: absolute; 122 | width: calc(100% - 75px); 123 | margin-top: 5px; 124 | margin-bottom: 10px; 125 | margin-left: var(--n-sidebar-width); 126 | 127 | z-index: 400; 128 | width: calc(100% - 60px); 129 | margin-top: 5px; 130 | left: 11px; 131 | } 132 | 133 | .clearIcon, .searchCategoryIcon, .clearIconCustomCategory, .searchCategoryIconCustomCategory, .clearIconCustomTemplate, .searchTemplateIconCustomTemplate, .clearIconCustomWorkflow, .searchWorkflowIconCustomWorkflow { 134 | position: absolute; 135 | padding: 5px; 136 | right: 30px; 137 | color: var(--input-text); 138 | font-size: larger; 139 | cursor: pointer; 140 | opacity: 0.5; 141 | user-select: none; 142 | line-height: 1.6; 143 | top: 0; 144 | 145 | 146 | display: flex; 147 | align-content: center; 148 | justify-content: center; 149 | align-items: center; 150 | height: -moz-available; /* WebKit-based browsers will ignore this. */ 151 | height: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */ 152 | height: fill-available; 153 | } 154 | 155 | 156 | .clearIcon:hover, .clearIconCustomCategory:hover, .clearIconCustomTemplate:hover, .clearIconCustomWorkflow:hover { 157 | opacity: 1.0; 158 | } 159 | .clearIcon,.clearIconCustomTemplate,.clearIconCustomWorkflow { 160 | line-height: 1.6 !important 161 | } 162 | 163 | .searchCategoryIcon, .searchCategoryIconCustomCategory, .searchTemplateIconCustomTemplate, .searchWorkflowIconCustomWorkflow { 164 | right: 0px; 165 | padding: 6px; 166 | font-size: larger; 167 | background: var(--border-color); 168 | color: var(--drag-text); 169 | margin: 4px; 170 | border-radius: 5px; 171 | 172 | /* text-align: center; */ 173 | height: -moz-available; /* WebKit-based browsers will ignore this. */ 174 | height: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */ 175 | height: fill-available; 176 | 177 | /* top: 0; */ 178 | /* box-sizing: content-box; */ 179 | display: flex; 180 | align-content: center; 181 | justify-content: center; 182 | line-height: 1; 183 | align-items: center; 184 | 185 | 186 | } 187 | 188 | .searchCategoryIcon:hover, .searchCategoryIconCustomCategory:hover, .searchTemplateIconCustomTemplate:hover, .searchWorkflowIconCustomWorkflow:hover { 189 | opacity: 0.5; 190 | } 191 | 192 | .sidebarCategory, #sidebarBookmarks { 193 | list-style-type: none; 194 | font-family: 'Open Sans', sans-serif; 195 | text-transform: capitalize; 196 | margin: 2px; 197 | background-color: var(--comfy-input-bg); 198 | opacity: 1; 199 | border-radius: 8px; 200 | padding-top: 11px; 201 | font-size: 15px; 202 | padding-bottom: 8px; 203 | } 204 | .sidebarCategory { 205 | border-bottom:var(--comfy-input-bg) solid 1px; 206 | text-wrap: nowrap; 207 | } 208 | #sidebarCustomNodes { 209 | list-style-type: none; 210 | font-family: 'Open Sans', sans-serif; 211 | text-transform: capitalize; 212 | margin: 0px; 213 | background-color: transparent; 214 | opacity: 1; 215 | border-radius: 8px; 216 | font-size: 15px; 217 | padding-left: 0; 218 | padding-right: 0px; 219 | padding-top: 0px; 220 | padding-bottom: 40px; 221 | } 222 | 223 | .sidebarItem:hover { 224 | background: color-mix(in srgb, currentColor 15%, transparent); 225 | } 226 | 227 | .sidebarItem { 228 | list-style-type: none; 229 | font-family: 'Open Sans', sans-serif; 230 | text-transform: capitalize; 231 | margin: 2px; 232 | background: color-mix(in srgb, var(--border-color) 35%, transparent); 233 | opacity: 1; 234 | border-radius: 8px; 235 | 236 | white-space: nowrap; 237 | text-overflow: ellipsis; 238 | overflow: hidden; 239 | /*max-width: calc(100% - 39px);*/ 240 | } 241 | 242 | .displayName{ 243 | float: left; 244 | width: 87%; 245 | text-overflow: ellipsis; 246 | overflow: hidden; 247 | white-space: nowrap; 248 | /* margin: 2% auto;*/ 249 | /*display: flex;*/ 250 | align-items: center; 251 | padding-left: 3%; 252 | /*height: 14px;*/ 253 | } 254 | .categoryName{ 255 | 256 | width: 100%; 257 | text-overflow: ellipsis; 258 | overflow: hidden; 259 | } 260 | 261 | /* Firefox-specific styles */ 262 | @-moz-document url-prefix() { 263 | .content_sidebar,#settingsContainer { 264 | scrollbar-width: thin; /* Scrollbar thickness */ 265 | scrollbar-color: hsla(0, 0%, 50%, .8) transparent; /* Thumb and track colors */ 266 | } 267 | #settingsContainer::-moz-scrollbar-thumb:hover, 268 | .content_sidebar::-moz-scrollbar-thumb:hover { 269 | background-color: rgba(150, 150, 150, 1); /* Thumb color on hover */ 270 | } 271 | 272 | #settingsContainer::-moz-scrollbar-thumb, 273 | .content_sidebar::-moz-scrollbar-thumb { 274 | background-color: hsla(0, 0%, 50%, .8); /* Thumb color */ 275 | border-radius: 9px; 276 | } 277 | } 278 | #settingsContainer::-webkit-scrollbar, 279 | .content_sidebar::-webkit-scrollbar { 280 | margin-top: 0.5rem; 281 | height: 1rem; 282 | width: .5rem; 283 | top: 10px; 284 | } 285 | #settingsContainer::-webkit-scrollbar:horizontal, 286 | .content_sidebar::-webkit-scrollbar:horizontal { 287 | height: .5rem; 288 | width: 1rem 289 | } 290 | 291 | #settingsContainer::-webkit-scrollbar-track, 292 | .content_sidebar::-webkit-scrollbar-track { 293 | background-color: transparent !important; 294 | border-radius: 9999px 295 | } 296 | 297 | #settingsContainer::-webkit-scrollbar-thumb, 298 | .content_sidebar::-webkit-scrollbar-thumb { 299 | --tw-border-opacity: 1; 300 | background-color: hsla(0, 0%, 50%, .8); 301 | border-color: rgba(255, 255, 255, 0, 0, 0); 302 | border-radius: 9999px; 303 | border-width: 1px 304 | } 305 | 306 | #settingsContainer::-webkit-scrollbar-thumb:hover, 307 | .content_sidebar::-webkit-scrollbar-thumb:hover { 308 | --tw-bg-opacity: 1; 309 | 310 | background-color: rgba(150, 150, 150, var(--tw-bg-opacity)) 311 | } 312 | 313 | #spacer { 314 | height: 45px; 315 | } 316 | 317 | 318 | .view_button { 319 | position: relative; 320 | float: unset; 321 | cursor: pointer; 322 | user-select: none; 323 | padding: 5px 0 5px; 324 | font-size: 20px; 325 | /*background-color: var(--comfy-input-bg);*/ 326 | opacity: 0.7; 327 | /*border-radius: 5px;*/ 328 | 329 | margin-top:2px; 330 | box-sizing: content-box; 331 | 332 | display: flex; 333 | justify-content: center; 334 | align-items: center; 335 | height: var(--sidebar-menu-item-width); 336 | /*border-right: 2px solid var(--content-bg);*/ 337 | border-color: var(--content-bg); 338 | 339 | } 340 | .view_button:hover { 341 | opacity: 1.0; 342 | } 343 | 344 | 345 | .sb_button_active { 346 | border-color: var(--content-hover-fg); 347 | color: var(--content-hover-fg); 348 | } 349 | #sidebar_views { 350 | position: relative; 351 | float: left; 352 | text-align: center; 353 | 354 | cursor: pointer; 355 | user-select: none; 356 | padding: 0px 2px 2px 2px; 357 | font-size: 20px; 358 | 359 | height: 100%; 360 | width: var(--n-sidebar-width); 361 | border: 0; 362 | border-left: 2px rgb(from var(--comfy-menu-bg) r g b / 100%); 363 | border-style: solid; 364 | padding-bottom: 0px; 365 | backdrop-filter: blur(16px) !important; 366 | background: rgb(from var(--comfy-menu-bg) r g b / 100%); 367 | z-index: 99999; 368 | } 369 | 370 | 371 | #searchInputSB.closed { 372 | display: none; 373 | } 374 | 375 | .content_sidebar.closed { 376 | width: 0 !important; 377 | } 378 | 379 | .searchCategoryIcon.closed { 380 | display: none; 381 | } 382 | 383 | .clearIcon.closed { 384 | display: none; 385 | } 386 | 387 | .sidebarCategory .pinButton, .sidebarItem .pinButton { 388 | background-color: transparent; 389 | border: 0; 390 | position: absolute; 391 | width: 10%; 392 | float: left; 393 | /* padding-top: 4px; */ 394 | /* font-size: 39px; */ 395 | height: 1.5em; 396 | } 397 | 398 | #sb_scrollToTopButton.closed { 399 | display: none !important; 400 | } 401 | 402 | #sidebarBookmarks .pinButton { 403 | 404 | background-color: transparent; 405 | border: 0; 406 | 407 | /*position: absolute; 408 | right: 0;*/ 409 | 410 | position: relative; 411 | width: 10%; 412 | float: left; 413 | 414 | } 415 | 416 | .previewButton { 417 | background-color: transparent; 418 | border: 0; 419 | position: absolute; 420 | right: 35px; 421 | } 422 | 423 | .pinned { 424 | fill: var(--descrip-text) !important; 425 | opacity: 1 !important; 426 | } 427 | 428 | .svg_class { 429 | width: 24px; 430 | height: 18px; 431 | fill: var(--border-color); 432 | cursor: pointer; 433 | /* hacky fix 434 | margin-top: -5px; 435 | margin-left: -25px;*/ 436 | } 437 | 438 | .pin_normal { 439 | opacity: 0.5; 440 | } 441 | 442 | .pin_normal:hover { 443 | opacity: 1; 444 | } 445 | 446 | .svg_class:hover { 447 | fill: var(--border-color); 448 | 449 | } 450 | 451 | 452 | #sidebarBookmarks .sidebarItem { 453 | margin-left: 0px; 454 | margin-right: 0px; 455 | 456 | } 457 | #sidebarBookmarks { 458 | width: calc(100% - 16px); 459 | margin-left: 3px; 460 | } 461 | .sb_label { 462 | font-family: 'Open Sans', sans-serif; 463 | position: relative; 464 | margin: 5px; 465 | font-weight: bold; 466 | 467 | background: var(--comfy-input-bg); 468 | border-radius: 3px; 469 | padding-left: 6px; 470 | padding-right: 6px; 471 | display: block; 472 | display: flex; 473 | width: calc(100% - 20px); 474 | text-align: center; 475 | user-select: none; 476 | cursor: pointer; 477 | 478 | align-items: center; 479 | height: 28px; 480 | justify-content: center; 481 | padding-top: 1px; 482 | } 483 | 484 | .expand_node, .pin_node { 485 | position: absolute !important; 486 | right: 5px; 487 | background: transparent; 488 | border: 0; 489 | 490 | } 491 | 492 | .expand_node svg, .pin_node svg { 493 | 494 | width: 20px; 495 | height: 20px; 496 | background: transparent; 497 | fill: var(--input-text); 498 | cursor: pointer; 499 | } 500 | 501 | .sb_dot { 502 | width: 8px; 503 | height: 8px; 504 | border-radius: 50%; 505 | background-color: grey; 506 | } 507 | 508 | .node_header { 509 | line-height: 1; 510 | padding: 8px 13px 7px; 511 | background: var(--comfy-input-bg); 512 | margin-bottom: 5px; 513 | font-size: 15px; 514 | text-wrap: nowrap; 515 | overflow: hidden; 516 | } 517 | 518 | .headdot { 519 | width: 10px; 520 | height: 10px; 521 | float: inline-start; 522 | margin-right: 8px; 523 | margin-top: 3px; 524 | } 525 | 526 | 527 | .IMAGE { 528 | background-color: #64b5f6; 529 | } 530 | 531 | .VAE { 532 | background-color: #ff6e6e; 533 | } 534 | 535 | .LATENT { 536 | background-color: #ff9cf9; 537 | } 538 | 539 | .MASK { 540 | background-color: #81c784; 541 | } 542 | 543 | .CONDITIONING { 544 | background-color: #ffa931; 545 | } 546 | 547 | .CLIP { 548 | background-color: #ffd500; 549 | } 550 | 551 | .MODEL { 552 | background-color: #b39ddb; 553 | } 554 | 555 | .CONTROL_NET { 556 | background-color: #a5d6a7; 557 | } 558 | 559 | 560 | #previewDiv { 561 | position: absolute; 562 | background-color: var(--comfy-menu-bg); 563 | font-family: 'Open Sans', sans-serif; 564 | font-size: small; 565 | color: var(--descrip-text); 566 | border: 1px solid var(--descrip-text); 567 | display: none; 568 | min-width: 300px; 569 | width: min-content; 570 | height: fit-content; 571 | z-index: 9999; 572 | border-radius: 12px; 573 | overflow: hidden; 574 | font-size: 12px; 575 | padding-bottom: 10px; 576 | 577 | } 578 | 579 | #previewDiv .sb_description { 580 | margin: 10px; 581 | padding: 6px; 582 | background: var(--border-color); 583 | border-radius: 5px; 584 | font-style: italic; 585 | font-weight: 500; 586 | font-size: 0.9rem; 587 | } 588 | 589 | .sb_table { 590 | display: grid; 591 | 592 | grid-column-gap: 10px; 593 | 594 | width: 100%; 595 | 596 | } 597 | 598 | .sb_row { 599 | display: grid; 600 | grid-template-columns: 10px 1fr 1fr 0fr 10px; 601 | grid-column-gap: 10px; 602 | align-items: baseline; 603 | padding-left: 9px; 604 | padding-right: 9px; 605 | } 606 | 607 | .sb_row_string { 608 | grid-template-columns: 10px 1fr 1fr 10fr 1fr; 609 | } 610 | 611 | .sb_col { 612 | border: 0px solid #000; 613 | display: flex; 614 | align-items: flex-end; 615 | flex-direction: row-reverse; 616 | flex-wrap: nowrap; 617 | align-content: flex-start; 618 | justify-content: flex-end; 619 | } 620 | 621 | .sb_inherit { 622 | display: inherit; 623 | width: max-content; 624 | flex: revert-layer; 625 | } 626 | 627 | .sb_inherit_orig { 628 | display: inherit; 629 | 630 | } 631 | 632 | .long_field { 633 | background: var(--bg-color); 634 | border: 2px solid var(--border-color); 635 | margin: 5px 5px 0 5px; 636 | border-radius: 10px; 637 | line-height: 1.7; 638 | } 639 | 640 | .sb_arrow { 641 | color: var(--fg-color); 642 | } 643 | 644 | .sb_preview_badge { 645 | text-align: center; 646 | background: var(--comfy-input-bg); 647 | font-weight: bold; 648 | color: var(--error-text); 649 | margin-bottom: 3px; 650 | } 651 | 652 | #sb_scrollToTopButton { 653 | position: absolute; 654 | bottom: 5px; 655 | right: 56px; 656 | cursor: pointer; 657 | display: none; 658 | 659 | border-radius: 20%; 660 | background: var(--comfy-menu-bg); 661 | padding: 4px; 662 | border: 1px solid #4c4c4c; 663 | } 664 | 665 | #sb_scrollToTopButton svg { 666 | width: 20px; 667 | height: 20px; 668 | fill: var(--descrip-text); 669 | } 670 | 671 | 672 | 673 | 674 | .panel_sidebar{ 675 | min-height: 200px; 676 | 677 | border-radius: 5px; 678 | margin-left: 7px; 679 | 680 | } 681 | 682 | .panel_sidebar label{ 683 | text-transform: uppercase; 684 | } 685 | 686 | 687 | 688 | 689 | 690 | 691 | .custom-menu { 692 | box-shadow: inset 0px 0px 0px rgb(44 44 44 / 5%), inset 1px -1px 0px 0px rgb(22 22 22 / 50%), 0px 0px 9px 0px rgb(0, 0, 0); 693 | font-family: Arial, sans-serif; 694 | display: none; 695 | position: absolute; 696 | background-color: var(--bg-color); 697 | border: 0px solid #313131; 698 | color: var(--input-text); 699 | padding: 5px 0; 700 | border-radius: 5px; 701 | z-index: 9999; 702 | /* backdrop-filter: blur(5px); 703 | background: rgb(from #353535 r g b / 50%);*/ 704 | } 705 | .custom-menu ul { 706 | list-style-type: none; 707 | padding: 0; 708 | margin: 0; 709 | width: 100%; 710 | } 711 | .custom-menu li { 712 | padding: 8px 15px; 713 | cursor: pointer; 714 | 715 | } 716 | #menu-options li { 717 | 718 | min-width: 134px; 719 | } 720 | 721 | 722 | 723 | .custom-menu .sub-menu li:first-child { 724 | border-top-left-radius: 5px; 725 | border-top-right-radius: 5px; 726 | } 727 | 728 | 729 | .custom-menu .sub-menu li:last-child { 730 | border-bottom-left-radius: 5px; 731 | border-bottom-right-radius: 5px; 732 | } 733 | .custom-menu li:hover { 734 | background-color: var(--border-color); 735 | } 736 | .sub-menu { 737 | 738 | box-shadow: inset 0px 0px 0px rgb(44 44 44 / 5%), inset 1px -1px 0px 0px rgb(22 22 22 / 50%), 0px 0px 9px 0px rgb(0, 0, 0); 739 | font-family: Arial, sans-serif; 740 | display: none; 741 | position: absolute; 742 | top: 0; 743 | left: 100%; 744 | background-color: var(--bg-color); 745 | border: 0px solid #313131; 746 | border-radius: 5px; 747 | border-top-left-radius: 0px; 748 | 749 | z-index: 1001; /* Ensure sub-menu appears above main menu */ 750 | /* 751 | backdrop-filter: blur(5px); 752 | background: rgb(from #353535 r g b / 50%);*/ 753 | } 754 | 755 | .cat_empty { 756 | display: none !important; 757 | } 758 | 759 | 760 | 761 | /* Settings */ 762 | #switch_settings{ 763 | /*bottom: 5px; 764 | position: absolute;*/ 765 | width: -moz-available; /* WebKit-based browsers will ignore this. */ 766 | width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */ 767 | width: fill-available; 768 | height: 20px; 769 | border: 0 !important; 770 | line-height: 1; 771 | box-sizing: content-box; 772 | } 773 | 774 | #sb_settingsDiv,#sb_settingsDiv_export, #sb_settingsDiv_import, #sb_settingsDiv_shortcut { 775 | outline: 0; 776 | border: 0; 777 | border-radius: 6px; 778 | background: var(--bg-color); 779 | color: var(--input-text); 780 | box-shadow: inset 1px 1px 0px rgba(255, 255, 255, 0.05), inset -1px -1px 0px rgba(0, 0, 0, 0.5), 2px 2px 20px rgb(0, 0, 0); 781 | max-width: 800px; 782 | width: 650px; 783 | box-sizing: border-box; 784 | font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 785 | font-size: 0.8rem; 786 | padding: 5px 20px 20px 20px; 787 | position: fixed; 788 | top: 50%; 789 | left: 50%; 790 | transform: translate(-50%, -50%); 791 | z-index: 1001; 792 | text-transform: uppercase; 793 | font-weight: bold; 794 | } 795 | #sb_settingsDiv_export, #sb_settingsDiv_import{ 796 | z-index: 1005; 797 | width: 30vh; 798 | } 799 | #sb_settingsDiv_shortcut { 800 | z-index: 1005; 801 | width: 100%; 802 | } 803 | #sb_settingsDiv .setting{ 804 | height: 28px; 805 | width: 100%; 806 | margin-bottom: 7px; 807 | } 808 | #sb_settingsDiv .setting input[type="range"]{ 809 | height: 28px; 810 | 811 | } 812 | 813 | #sb_settingsDiv .setting input[type="text"], #sb_settingsDiv_shortcut .setting input[type="text"]{ 814 | background: var(--comfy-menu-bg); 815 | color: var(--drag-text); 816 | padding: 5px; 817 | float: right; 818 | width: 60%; 819 | margin-right: 0px; 820 | border-radius: 5px; 821 | border: solid 1px var(--descrip-text); 822 | } 823 | 824 | #sb_settingsDiv_shortcut .setting input[type="text"] { 825 | 826 | width: 48% !important; 827 | } 828 | #sb_settingsDiv .setting textarea{ 829 | background: var(--comfy-menu-bg); 830 | color: var(--drag-text); 831 | padding: 5px; 832 | float: right; 833 | width: calc(100% - 60px); 834 | resize: vertical; 835 | margin-right: 47px; 836 | border-radius: 5px; 837 | border: solid 1px var(--descrip-text); 838 | margin-bottom: 20px; 839 | } 840 | #sb_settingsDiv .setting input[type="checkbox"], #sb_settingsDiv_export .setting input[type="checkbox"], #sb_settingsDiv_import .setting input[type="checkbox"]{ 841 | width: 18px; 842 | height: 18px; 843 | } 844 | #sb_settingsDiv_export .setting, #sb_settingsDiv_import .setting , #sb_settingsDiv_shortcut .setting{ 845 | height: 36px; 846 | } 847 | #sb_settingsDiv .setting input, #sb_settingsDiv_export .setting input , #sb_settingsDiv_import .setting input, #sb_settingsDiv_shortcut .setting input{ 848 | float: right; 849 | } 850 | #sb_settingsDiv .setting label{ 851 | line-height: 1.8; 852 | } 853 | #sb_settingsDiv .slider-container{ 854 | float: right; 855 | width: 36px; 856 | background: var(--comfy-menu-bg); 857 | padding: 3px; 858 | border-radius: 5px; 859 | text-align: center; 860 | margin: 5px auto; 861 | } 862 | 863 | #sb_settingsDiv_export h1, #sb_settingsDiv_import h1, #sb_settingsDiv_shortcut h1{ 864 | font-size: medium; 865 | 866 | } 867 | 868 | #sb_settingsDiv h1,#sb_settingsDiv_export h1, #sb_settingsDiv_import h1 , #sb_settingsDiv_shortcut h1{ 869 | margin: 0 0 20px 0; 870 | padding: 0 0 5px 0; 871 | text-align: center; 872 | border-bottom: 5px solid var(--border-color); 873 | text-transform: uppercase; 874 | } 875 | 876 | #settingsContainer button{ 877 | 878 | padding: 9px; 879 | /* position: absolute; */ 880 | bottom: 0; 881 | width: 100%; 882 | margin: 0 0 20px 0; 883 | background: var(--comfy-menu-bg); 884 | color: var(--input-text); 885 | border: 1px solid var(--border-color); 886 | border-radius: 5px; 887 | cursor: pointer; 888 | text-transform: uppercase; 889 | font-weight: bold; 890 | } 891 | #settingsContainer button:hover{ 892 | filter: brightness(120%); 893 | } 894 | #settingsContainer{ 895 | height: 50vh; 896 | overflow-x: hidden; 897 | overflow-y: auto; 898 | margin-right: -10px; 899 | padding-right: 10px; 900 | } 901 | #sb_buttonsContainer { 902 | display: grid; 903 | grid-template-columns: repeat(2, 1fr); /* Due colonne, larghezza uguale */ 904 | grid-template-rows: auto; /* Le righe si adattano all'altezza degli elementi */ 905 | gap: 5px; /* Spaziatura tra i bottoni */ 906 | width: 100%; /* Si adatta alla larghezza del contenitore padre */ 907 | margin-bottom: 8px; 908 | } 909 | 910 | #sb_buttonsContainer button, #sb_buttonsContainerCustomize button { 911 | padding: 9px; 912 | width: 100%; /* Il bottone si adatta alla colonna */ 913 | background: var(--comfy-menu-bg); 914 | color: var(--input-text); 915 | border: 1px solid var(--border-color); 916 | margin: 0 0 0px 0; 917 | border-radius: 5px; 918 | cursor: pointer; 919 | text-transform: uppercase; 920 | font-weight: bold; 921 | box-sizing: border-box; /* Include il padding e il bordo nel calcolo della larghezza */ 922 | } 923 | 924 | #sb_buttonsContainer #sb_runExportButton{ 925 | width: 200%; 926 | } 927 | 928 | 929 | 930 | #sb_buttonsContainerCustomize{ 931 | display: grid; 932 | grid-template-columns: repeat(3, 1fr); /* Due colonne, larghezza uguale */ 933 | grid-template-rows: auto; /* Le righe si adattano all'altezza degli elementi */ 934 | gap: 5px; /* Spaziatura tra i bottoni */ 935 | width: 100%; /* Si adatta alla larghezza del contenitore padre */ 936 | margin-bottom: 8px; 937 | } 938 | 939 | #sb_settingsDiv i{ 940 | margin-left: 4px; 941 | color: #0075ff; 942 | font-style: normal; 943 | font-size: larger; 944 | } 945 | 946 | .remove-section-btn{ 947 | padding: 5px; 948 | position: static; 949 | float: right; 950 | right: 25px; 951 | } 952 | /* MODALS */ 953 | .sb-modal-title{ 954 | position: absolute; 955 | top: 4px; 956 | margin-top: 0; 957 | text-transform: uppercase; 958 | } 959 | 960 | .sb-modal-backdrop, .sb-modal-backdrop-settings { 961 | position: fixed; 962 | top: 0; 963 | left: 0; 964 | width: 100%; 965 | height: 100%; 966 | background-color: rgba(0, 0, 0, 0.2); /* Add desired opacity */ 967 | z-index: 1000; /* Make sure the backdrop is behind the modal */ 968 | } 969 | 970 | 971 | #sb_settingsDiv select{ 972 | background: var(--comfy-menu-bg); 973 | color: var(--drag-text); 974 | padding: 5px; 975 | float: right; 976 | width: 129px; 977 | margin-right: 47px; 978 | border-radius: 5px; 979 | } 980 | 981 | .colorBlob { 982 | float: right; 983 | overflow: hidden; 984 | margin-top: 34px; 985 | width: 100%; 986 | height: 125px; 987 | border-radius: 6px; 988 | } 989 | 990 | .colorBlob input[type='color'] { 991 | width: 106%; 992 | height: 112%; 993 | margin: -6px; 994 | } 995 | 996 | 997 | 998 | /*#switch_home,#switch_custom_view{ 999 | width: 1.3rem; 1000 | height: 1.3rem; 1001 | 1002 | }*/ 1003 | 1004 | #switch_sidebar{ 1005 | 1006 | margin-top: 5px; 1007 | } 1008 | 1009 | #sbModal{ 1010 | display: none; 1011 | z-index: 9999; 1012 | } 1013 | 1014 | .full_rounded{ 1015 | border-radius: 5px; 1016 | } 1017 | 1018 | .p-button { 1019 | overflow: visible !important; 1020 | 1021 | } 1022 | /*************/ 1023 | /*ALPHA: INGLOBE SIDEBAR */ 1024 | 1025 | .splitter-overlay{ 1026 | /*width: 486px !important;*/ 1027 | z-index: 100 !important; 1028 | 1029 | 1030 | } 1031 | 1032 | 1033 | 1034 | #official_button{ 1035 | display: flex; 1036 | flex-direction: column; 1037 | position: absolute; 1038 | height: auto; 1039 | bottom: 50%; 1040 | 1041 | } 1042 | 1043 | #official_button button{ 1044 | width: var(--n-sidebar-width); 1045 | height: var(--n-sidebar-width); 1046 | } 1047 | 1048 | #official_button i { 1049 | font-size: 20px !important; 1050 | } 1051 | 1052 | /* setup tooltips */ 1053 | 1054 | #openModalButton{ 1055 | z-index: 1000 !important; 1056 | } 1057 | 1058 | 1059 | [data-tooltip] { 1060 | --arrow-size: 5px; 1061 | position: relative; 1062 | /*z-index: 10;*/ 1063 | } 1064 | 1065 | /* Positioning and visibility settings of the tooltip */ 1066 | [data-tooltip]:before, 1067 | [data-tooltip]:after { 1068 | position: absolute; 1069 | visibility: hidden; 1070 | opacity: 0; 1071 | left: 50%; 1072 | bottom: calc(100% + var(--arrow-size)); 1073 | pointer-events: none; 1074 | transition: 0.2s; 1075 | will-change: transform; 1076 | } 1077 | 1078 | /* The actual tooltip with a dynamic width */ 1079 | [data-tooltip]:before { 1080 | content: attr(data-tooltip); 1081 | padding: 10px 18px; 1082 | min-width: 50px; 1083 | max-width: 350px; 1084 | width: max-content; 1085 | width: -moz-max-content; 1086 | border-radius: 6px; 1087 | font-size: 14px; 1088 | background-color: var(--border-color);; 1089 | background-image: linear-gradient(30deg, 1090 | rgba(59, 72, 80, 0.44), 1091 | rgba(59, 68, 75, 0.44), 1092 | rgba(60, 82, 88, 0.44)); 1093 | box-shadow: 0px 0px 24px rgba(0, 0, 0, 0.2); 1094 | color: #fff; 1095 | white-space: pre-wrap; 1096 | transform: translate(-50%, calc(0px - var(--arrow-size))) scale(0.5); 1097 | z-index: 10; 1098 | } 1099 | 1100 | /* Tooltip arrow */ 1101 | [data-tooltip]:after { 1102 | content: ''; 1103 | border-style: solid; 1104 | border-width: var(--arrow-size) var(--arrow-size) 0px var(--arrow-size); /* CSS triangle */ 1105 | border-color: var(--border-color) transparent transparent transparent; 1106 | transition-duration: 0s; /* If the mouse leaves the element, 1107 | the transition effects for the 1108 | tooltip arrow are "turned off" */ 1109 | transform-origin: top; /* Orientation setting for the 1110 | slide-down effect */ 1111 | transform: translateX(-50%) scaleY(0); 1112 | } 1113 | 1114 | /* Tooltip becomes visible at hover */ 1115 | [data-tooltip]:hover:before, 1116 | [data-tooltip]:hover:after { 1117 | visibility: visible; 1118 | opacity: 1; 1119 | } 1120 | /* Scales from 0.5 to 1 -> grow effect */ 1121 | [data-tooltip]:hover:before { 1122 | transition-delay: 0.8s; 1123 | transform: translate(-50%, calc(0px - var(--arrow-size))) scale(1); 1124 | } 1125 | /* 1126 | Arrow slide down effect only on mouseenter (NOT on mouseleave) 1127 | */ 1128 | [data-tooltip]:hover:after { 1129 | transition-delay: 0.8s; /* Starting after the grow effect */ 1130 | transition-duration: 0.2s; 1131 | transform: translateX(-50%) scaleY(1); 1132 | } 1133 | /* 1134 | That's it for the basic tooltip. 1135 | 1136 | If you want some adjustability 1137 | here are some orientation settings you can use: 1138 | */ 1139 | 1140 | /* LEFT */ 1141 | /* Tooltip + arrow */ 1142 | .left:before, 1143 | .left:after { 1144 | left: auto; 1145 | right: calc(100% + var(--arrow-size)); 1146 | bottom: 50%; 1147 | } 1148 | 1149 | /* Tooltip */ 1150 | .left:before { 1151 | transform: translate(calc(0px - var(--arrow-size)), 50%) scale(0.5); 1152 | } 1153 | .left:hover:before { 1154 | transform: translate(calc(0px - var(--arrow-size)), 50%) scale(1); 1155 | } 1156 | 1157 | /* Arrow */ 1158 | .left:after { 1159 | border-width: var(--arrow-size) 0px var(--arrow-size) var(--arrow-size); 1160 | border-color: transparent transparent transparent var(--border-color);; 1161 | transform-origin: left; 1162 | transform: translateY(50%) scaleX(0); 1163 | } 1164 | .left:hover:after { 1165 | transform: translateY(50%) scaleX(1); 1166 | } 1167 | 1168 | 1169 | 1170 | /* RIGHT */ 1171 | .right:before, 1172 | .right:after { 1173 | left: calc(100% + var(--arrow-size)); 1174 | bottom: 50%; 1175 | } 1176 | 1177 | .right:before { 1178 | transform: translate(var(--arrow-size), 50%) scale(0.5); 1179 | } 1180 | .right:hover:before { 1181 | transform: translate(var(--arrow-size), 50%) scale(1); 1182 | } 1183 | 1184 | .right:after { 1185 | border-width: var(--arrow-size) var(--arrow-size) var(--arrow-size) 0px; 1186 | border-color: transparent var(--border-color) transparent transparent; 1187 | transform-origin: right; 1188 | transform: translateY(50%) scaleX(0); 1189 | } 1190 | .right:hover:after { 1191 | transform: translateY(50%) scaleX(1); 1192 | } 1193 | 1194 | 1195 | 1196 | /* BOTTOM */ 1197 | [data-tooltip-location="bottom"]:before, 1198 | [data-tooltip-location="bottom"]:after { 1199 | top: calc(100% + var(--arrow-size)); 1200 | bottom: auto; 1201 | } 1202 | 1203 | [data-tooltip-location="bottom"]:before { 1204 | transform: translate(-50%, var(--arrow-size)) scale(0.5); 1205 | } 1206 | [data-tooltip-location="bottom"]:hover:before { 1207 | transform: translate(-50%, var(--arrow-size)) scale(1); 1208 | } 1209 | 1210 | [data-tooltip-location="bottom"]:after { 1211 | border-width: 0px var(--arrow-size) var(--arrow-size) var(--arrow-size); 1212 | border-color: transparent transparent var(--border-color) transparent; 1213 | transform-origin: bottom; 1214 | } 1215 | 1216 | 1217 | 1218 | /* Settings that make the pen look nicer */ 1219 | 1220 | @keyframes moveFocus { 1221 | 0% { background-position: 0% 100% } 1222 | 100% { background-position: 100% 0% } 1223 | } 1224 | 1225 | 1226 | 1227 | @media (max-height: 450px) { 1228 | main { 1229 | margin: 2rem 0; 1230 | } 1231 | } 1232 | 1233 | @media (max-width: 800px) { 1234 | html { 1235 | font-size: 0.9em; 1236 | } 1237 | } 1238 | 1239 | 1240 | 1241 | 1242 | 1243 | 1244 | 1245 | 1246 | 1247 | 1248 | /*SETTINGS TAB&CUSTOMIZE*/ 1249 | 1250 | .n-sb-tab-container { 1251 | margin-bottom: 1rem; 1252 | display: grid; 1253 | grid-template-columns: repeat(2, 1fr); 1254 | grid-template-rows: auto; 1255 | gap: 5px; 1256 | width: 100%; 1257 | margin-bottom: 8px; 1258 | } 1259 | 1260 | .n-sb-tab { 1261 | padding: 10px 20px; 1262 | cursor: pointer; 1263 | margin-right: 5px; 1264 | border-bottom: 3px solid rgb(255, 255, 255); 1265 | text-align: center; 1266 | } 1267 | 1268 | .n-sb-tab.n-sb-active { 1269 | 1270 | border-color: #0075ff; 1271 | } 1272 | 1273 | 1274 | .n-sb-tab-content { 1275 | display: none; 1276 | height: 50vh; 1277 | overflow-y: auto; 1278 | } 1279 | 1280 | .n-sb-active { 1281 | display: block !important; 1282 | } 1283 | 1284 | .sb_hidden { 1285 | display: none; 1286 | } 1287 | 1288 | .n-sb-tab-container .n-sb-tab { 1289 | padding: 9px; 1290 | width: 100%; 1291 | color: var(--input-text); 1292 | margin: 0 0 0px 0; 1293 | cursor: pointer; 1294 | text-transform: uppercase; 1295 | font-weight: bold; 1296 | box-sizing: border-box; 1297 | font-size: medium; 1298 | } 1299 | 1300 | .n-sb-section { 1301 | display: flex; 1302 | flex-direction: column; 1303 | margin-bottom: 20px; 1304 | padding: 10px; 1305 | 1306 | border-radius: 5px; 1307 | background-color: var(--comfy-menu-bg); 1308 | } 1309 | .n-sb-section-header { 1310 | display: flex; 1311 | align-items: center; 1312 | margin-bottom: 10px; 1313 | } 1314 | .n-sb-section-header input { 1315 | margin-left: 10px; 1316 | height: 23px; 1317 | } 1318 | .n-sb-section-panels { 1319 | 1320 | margin-bottom: 10px; 1321 | } 1322 | .n-sb-section-panels .add-panel-btn{ 1323 | padding: 5px; 1324 | float: right; 1325 | } 1326 | .draggable { 1327 | cursor: move; 1328 | } 1329 | .panel-list { 1330 | list-style-type: none; 1331 | padding: 0; 1332 | } 1333 | .panel-list li { 1334 | margin-bottom: 5px; 1335 | margin-right: 5px; 1336 | padding: 5px !important; 1337 | background: var(--bg-color); 1338 | } 1339 | .panel-select{ 1340 | margin-left: 9px !important; 1341 | width: 70% !important; 1342 | float: left !important; 1343 | } 1344 | .remove-panel { 1345 | bottom: 0; 1346 | float: right; 1347 | background: var(--comfy-menu-bg); 1348 | color: var(--input-text); 1349 | border: 1px solid var(--border-color); 1350 | border-radius: 5px; 1351 | cursor: pointer; 1352 | text-transform: uppercase; 1353 | font-weight: bold; 1354 | } 1355 | .n-sb-section-header label { 1356 | padding: 0 0 0px 10px; 1357 | } 1358 | .icon-input{ 1359 | width: 20px; 1360 | text-align: center; 1361 | } -------------------------------------------------------------------------------- /app/html/sidebar.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/img/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/js/fts_fuzzy_match.js: -------------------------------------------------------------------------------- 1 | // LICENSE 2 | // 3 | // This software is dual-licensed to the public domain and under the following 4 | // license: you are granted a perpetual, irrevocable license to copy, modify, 5 | // publish, and distribute this file as you see fit. 6 | // 7 | // VERSION 8 | // 0.1.0 (2016-03-28) Initial release 9 | // 10 | // AUTHOR 11 | // Forrest Smith 12 | // 13 | // CONTRIBUTORS 14 | // J�rgen Tjern� - async helper 15 | // Anurag Awasthi - updated to 0.2.0 16 | 17 | const SEQUENTIAL_BONUS = 25; // bonus for adjacent matches 18 | const SEPARATOR_BONUS = 30; // bonus if match occurs after a separator 19 | const CAMEL_BONUS = 30; // bonus if match is uppercase and prev is lower 20 | const FIRST_LETTER_BONUS = 15; // bonus if the first letter is matched 21 | 22 | const LEADING_LETTER_PENALTY = -5; // penalty applied for every letter in str before the first match 23 | const MAX_LEADING_LETTER_PENALTY = -15; // maximum penalty for leading letters 24 | const UNMATCHED_LETTER_PENALTY = -1; 25 | 26 | /** 27 | * Returns true if each character in pattern is found sequentially within str 28 | * @param {*} pattern string 29 | * @param {*} str string 30 | */ 31 | function fuzzyMatchSimple(pattern, str) { 32 | let patternIdx = 0; 33 | let strIdx = 0; 34 | const patternLength = pattern.length; 35 | const strLength = str.length; 36 | 37 | while (patternIdx != patternLength && strIdx != strLength) { 38 | const patternChar = pattern.charAt(patternIdx).toLowerCase(); 39 | const strChar = str.charAt(strIdx).toLowerCase(); 40 | if (patternChar == strChar) ++patternIdx; 41 | ++strIdx; 42 | } 43 | 44 | return patternLength != 0 && strLength != 0 && patternIdx == patternLength 45 | ? true 46 | : false; 47 | } 48 | 49 | /** 50 | * Does a fuzzy search to find pattern inside a string. 51 | * @param {*} pattern string pattern to search for 52 | * @param {*} str string string which is being searched 53 | * @returns [boolean, number] a boolean which tells if pattern was 54 | * found or not and a search score 55 | */ 56 | function fuzzyMatch(pattern, str) { 57 | const recursionCount = 0; 58 | const recursionLimit = 10; 59 | const matches = []; 60 | const maxMatches = 256; 61 | 62 | return fuzzyMatchRecursive( 63 | pattern, 64 | str, 65 | 0 /* patternCurIndex */, 66 | 0 /* strCurrIndex */, 67 | null /* srcMatces */, 68 | matches, 69 | maxMatches, 70 | 0 /* nextMatch */, 71 | recursionCount, 72 | recursionLimit 73 | ); 74 | } 75 | 76 | function fuzzyMatchRecursive( 77 | pattern, 78 | str, 79 | patternCurIndex, 80 | strCurrIndex, 81 | srcMatces, 82 | matches, 83 | maxMatches, 84 | nextMatch, 85 | recursionCount, 86 | recursionLimit 87 | ) { 88 | let outScore = 0; 89 | 90 | // Return if recursion limit is reached. 91 | if (++recursionCount >= recursionLimit) { 92 | return [false, outScore]; 93 | } 94 | 95 | // Return if we reached ends of strings. 96 | if (patternCurIndex === pattern.length || strCurrIndex === str.length) { 97 | return [false, outScore]; 98 | } 99 | 100 | // Recursion params 101 | let recursiveMatch = false; 102 | let bestRecursiveMatches = []; 103 | let bestRecursiveScore = 0; 104 | 105 | // Loop through pattern and str looking for a match. 106 | let firstMatch = true; 107 | while (patternCurIndex < pattern.length && strCurrIndex < str.length) { 108 | // Match found. 109 | if ( 110 | pattern[patternCurIndex].toLowerCase() === str[strCurrIndex].toLowerCase() 111 | ) { 112 | if (nextMatch >= maxMatches) { 113 | return [false, outScore]; 114 | } 115 | 116 | if (firstMatch && srcMatces) { 117 | matches = [...srcMatces]; 118 | firstMatch = false; 119 | } 120 | 121 | const recursiveMatches = []; 122 | const [matched, recursiveScore] = fuzzyMatchRecursive( 123 | pattern, 124 | str, 125 | patternCurIndex, 126 | strCurrIndex + 1, 127 | matches, 128 | recursiveMatches, 129 | maxMatches, 130 | nextMatch, 131 | recursionCount, 132 | recursionLimit 133 | ); 134 | 135 | if (matched) { 136 | // Pick best recursive score. 137 | if (!recursiveMatch || recursiveScore > bestRecursiveScore) { 138 | bestRecursiveMatches = [...recursiveMatches]; 139 | bestRecursiveScore = recursiveScore; 140 | } 141 | recursiveMatch = true; 142 | } 143 | 144 | matches[nextMatch++] = strCurrIndex; 145 | ++patternCurIndex; 146 | } 147 | ++strCurrIndex; 148 | } 149 | 150 | const matched = patternCurIndex === pattern.length; 151 | 152 | if (matched) { 153 | outScore = 100; 154 | 155 | // Apply leading letter penalty 156 | let penalty = LEADING_LETTER_PENALTY * matches[0]; 157 | penalty = 158 | penalty < MAX_LEADING_LETTER_PENALTY 159 | ? MAX_LEADING_LETTER_PENALTY 160 | : penalty; 161 | outScore += penalty; 162 | 163 | //Apply unmatched penalty 164 | const unmatched = str.length - nextMatch; 165 | outScore += UNMATCHED_LETTER_PENALTY * unmatched; 166 | 167 | // Apply ordering bonuses 168 | for (let i = 0; i < nextMatch; i++) { 169 | const currIdx = matches[i]; 170 | 171 | if (i > 0) { 172 | const prevIdx = matches[i - 1]; 173 | if (currIdx == prevIdx + 1) { 174 | outScore += SEQUENTIAL_BONUS; 175 | } 176 | } 177 | 178 | // Check for bonuses based on neighbor character value. 179 | if (currIdx > 0) { 180 | // Camel case 181 | const neighbor = str[currIdx - 1]; 182 | const curr = str[currIdx]; 183 | if ( 184 | neighbor !== neighbor.toUpperCase() && 185 | curr !== curr.toLowerCase() 186 | ) { 187 | outScore += CAMEL_BONUS; 188 | } 189 | const isNeighbourSeparator = neighbor == "_" || neighbor == " "; 190 | if (isNeighbourSeparator) { 191 | outScore += SEPARATOR_BONUS; 192 | } 193 | } else { 194 | // First letter 195 | outScore += FIRST_LETTER_BONUS; 196 | } 197 | } 198 | 199 | // Return best result 200 | if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) { 201 | // Recursive score is better than "this" 202 | matches = [...bestRecursiveMatches]; 203 | outScore = bestRecursiveScore; 204 | return [true, outScore]; 205 | } else if (matched) { 206 | // "this" score is better than recursive 207 | return [true, outScore]; 208 | } else { 209 | return [false, outScore]; 210 | } 211 | } 212 | return [false, outScore]; 213 | } 214 | 215 | /** 216 | * Strictly optional utility to help make using fts_fuzzy_match easier for large data sets 217 | * Uses setTimeout to process matches before a maximum amount of time before sleeping 218 | * 219 | * To use: 220 | * const asyncMatcher = new ftsFuzzyMatchAsync(fuzzyMatch, "fts", "ForrestTheWoods", 221 | * function(results) { console.log(results); }); 222 | * asyncMatcher.start(); 223 | * 224 | * @param {*} matchFn function Matching function - fuzzyMatchSimple or fuzzyMatch. 225 | * @param {*} pattern string Pattern to search for. 226 | * @param {*} dataSet array Array of string in which pattern is searched. 227 | * @param {*} onComplete function Callback function which is called after search is complete. 228 | */ 229 | function ftsFuzzyMatchAsync(matchFn, pattern, dataSet, onComplete) { 230 | const ITEMS_PER_CHECK = 1000; // performance.now can be very slow depending on platform 231 | const results = []; 232 | const max_ms_per_frame = 1000.0 / 30.0; // 30FPS 233 | let dataIndex = 0; 234 | let resumeTimeout = null; 235 | 236 | // Perform matches for at most max_ms 237 | function step() { 238 | clearTimeout(resumeTimeout); 239 | resumeTimeout = null; 240 | 241 | var stopTime = performance.now() + max_ms_per_frame; 242 | 243 | for (; dataIndex < dataSet.length; ++dataIndex) { 244 | if (dataIndex % ITEMS_PER_CHECK == 0) { 245 | if (performance.now() > stopTime) { 246 | resumeTimeout = setTimeout(step, 1); 247 | return; 248 | } 249 | } 250 | 251 | var str = dataSet[dataIndex]; 252 | var result = matchFn(pattern, str); 253 | 254 | // A little gross because fuzzy_match_simple and fuzzy_match return different things 255 | if (matchFn === fuzzyMatchSimple && result == true) results.push(str); 256 | else if (matchFn === fuzzyMatch && result[0] == true) results.push([result[1], str]); 257 | } 258 | 259 | onComplete(results); 260 | return null; 261 | } 262 | 263 | // Abort current process 264 | this.cancel = function() { 265 | if (resumeTimeout !== null) clearTimeout(resumeTimeout); 266 | }; 267 | 268 | // Must be called to start matching. 269 | // I tried to make asyncMatcher auto-start via "var resumeTimeout = step();" 270 | // However setTimout behaving in an unexpected fashion as onComplete insisted on triggering twice. 271 | this.start = function() { 272 | step(); 273 | }; 274 | 275 | // Process full list. Blocks script execution until complete 276 | this.flush = function() { 277 | max_ms_per_frame = Infinity; 278 | step(); 279 | }; 280 | } 281 | 282 | export { fuzzyMatch, fuzzyMatchSimple, ftsFuzzyMatchAsync }; 283 | -------------------------------------------------------------------------------- /app/js/functions/sb_fn.js: -------------------------------------------------------------------------------- 1 | 2 | //load folder name 3 | 4 | 5 | function getNameFolderSync() { 6 | const xhr = new XMLHttpRequest(); 7 | xhr.open("GET", "sidebar/current", false); // false = sincrona (blasfemia) 8 | xhr.send(null); 9 | 10 | if (xhr.status === 200) { 11 | return JSON.parse(xhr.responseText); 12 | } else { 13 | throw new Error("Errore nella richiesta"); 14 | } 15 | } 16 | const nameFolder = getNameFolderSync(); 17 | const cnPath = `../extensions/${nameFolder}/`; 18 | 19 | function addSidebarStyles(cssPath) { 20 | const timestamp = new Date().getTime(); 21 | const linkElement = document.createElement("link"); 22 | linkElement.rel = "stylesheet"; 23 | linkElement.type = "text/css"; 24 | linkElement.href =`${cnPath}${cssPath}?v=${timestamp}` 25 | document.head.appendChild(linkElement); 26 | } 27 | 28 | 29 | function sidebarAddNode(name, text, x, y) { 30 | const node = LiteGraph.createNode(name, text) 31 | if (node) { 32 | const pos = 33 | [ 34 | x, 35 | y 36 | ] 37 | node.pos = pos; 38 | app.graph.add(node) 39 | } 40 | } 41 | 42 | function sdExpandAll(forceExpand = 0) { 43 | const categoryItems = document.querySelectorAll(".content_sidebar .sidebarCategory"); 44 | const side_bar_status = document.querySelector(".content_sidebar").dataset.expanded; 45 | const expand_nodes = document.querySelectorAll(".expand_node"); 46 | 47 | let display_value = "true"; 48 | 49 | if (side_bar_status === "true" && forceExpand === 0) { 50 | display_value = "none"; 51 | expand_nodes.forEach(node => { 52 | if (node.tagName.toLowerCase() === "button") { 53 | node.innerHTML = ` 55 | 57 | 59 | 62 | 63 | `; 64 | } 65 | }); 66 | document.querySelector(".content_sidebar").dataset.expanded = "false"; 67 | 68 | } else { 69 | display_value = "block"; 70 | expand_nodes.forEach(node => { 71 | if (node.tagName.toLowerCase() === "button") { 72 | node.innerHTML = ` 73 | 74 | 76 | 78 | 79 | 80 | `; 81 | } 82 | }); 83 | document.querySelector(".content_sidebar").dataset.expanded = "true"; 84 | } 85 | 86 | categoryItems.forEach(function (categoryItem) { 87 | const displayNamesList = categoryItem.querySelector("ul"); 88 | 89 | if (displayNamesList) { 90 | displayNamesList.style.display = display_value; 91 | } 92 | }); 93 | } 94 | 95 | 96 | 97 | function handleSearch(categorySearchToggle,searchSection,searchIdInput) { 98 | 99 | 100 | return new Promise((resolve, reject) => { 101 | const options = { 102 | keys: ['textContent','category'], 103 | threshold: 0.5, 104 | useExtendedSearch: true 105 | }; 106 | 107 | 108 | const searchTerm = document.getElementById(searchIdInput).value.toLowerCase(); 109 | const categoryItems = document.querySelectorAll(".sidebarCategory li"); 110 | const categories = document.querySelectorAll(searchSection+" .sidebarCategory"); 111 | const listItems = document.querySelectorAll(searchSection+" .sidebarItem"); 112 | 113 | 114 | /*reset*/ 115 | categoryItems.forEach(category => { 116 | const subItems = category.querySelectorAll("li"); 117 | category.style.display = "block"; 118 | 119 | subItems.forEach(sub => { 120 | sub.style.display = "block"; 121 | }); 122 | 123 | }); 124 | 125 | 126 | 127 | const sidebarItems = categorySearchToggle ? categories : listItems; 128 | 129 | sidebarItems.forEach(item => { 130 | let itemText = item.textContent.toLowerCase(); 131 | if (categorySearchToggle) { 132 | 133 | itemText = Array.from(item.childNodes) 134 | .filter(node => node.nodeType === Node.TEXT_NODE) 135 | .map(node => node.textContent.trim()) 136 | .join(' ').toLowerCase(); 137 | } 138 | 139 | //fts_fuzzy_match(searchTerm, cleanText(itemText)) 140 | const isInSearchTerm = itemText.includes(searchTerm); 141 | item.style.display = isInSearchTerm ? "block" : "none"; 142 | }); 143 | 144 | /* hide empty categories */ 145 | categories.forEach(category => { 146 | const subItems = category.querySelectorAll("li"); 147 | const areAllHidden = Array.from(subItems).every(subItem => subItem.style.display === "none"); 148 | category.style.display = areAllHidden ? "none" : category.style.display; 149 | }); 150 | 151 | sdExpandAll(1); 152 | resolve(searchTerm); 153 | }); 154 | } 155 | 156 | //PREVIEW 157 | function safeObjectKeys(obj) { 158 | try { 159 | return Object.keys(obj); 160 | } catch (error) { 161 | //console.error('Error while trying to get object keys:', error); 162 | return []; 163 | } 164 | } 165 | 166 | function createNodePreview(nodeID) { 167 | let description = ""; 168 | let rows = ""; 169 | let last_rows = ""; 170 | let error = ''; 171 | const data = LiteGraph.registered_node_types; 172 | 173 | 174 | try{ 175 | description = data[nodeID].nodeData.description; 176 | }catch(err){ 177 | description = ""; 178 | } 179 | try{ 180 | let inputs = data[nodeID].nodeData.input; //divided between optional and required 181 | let outputs_name = data[nodeID].nodeData.output_name; 182 | let outputs = data[nodeID].nodeData.output; 183 | //console.log(category, inputs, outputs); 184 | //create array with objectkeys from input and output 185 | const inputArray = []; 186 | const outputArray = []; 187 | 188 | const inputKeysRequired = safeObjectKeys(inputs.required) 189 | const inputKeysOptional = safeObjectKeys(inputs.optional) 190 | 191 | 192 | for (let i = 0; i < inputKeysRequired.length; i++) { 193 | try{ 194 | let thirdV = null; 195 | let secondV = inputs.required[inputKeysRequired[i]][0]; 196 | 197 | if (Array.isArray(secondV)){ 198 | //array 199 | secondV = inputs.required[inputKeysRequired[i]][0][0]; 200 | thirdV = inputs.required[inputKeysRequired[i]][0][0]; 201 | } 202 | 203 | let isArr = false 204 | 205 | try { 206 | if( typeof inputs.required[inputKeysRequired[i]][0] === 'object'){ 207 | isArr = true 208 | } 209 | } 210 | catch(err) { 211 | console.log("error",err) 212 | } 213 | if (inputs.required[inputKeysRequired[i]][1]!=undefined && Object.keys(inputs.required[inputKeysRequired[i]][1]).length > 0){ 214 | 215 | 216 | if (!isArr){ 217 | //object 218 | thirdV = inputs.required[inputKeysRequired[i]][1].default; 219 | } 220 | else{ 221 | thirdV = inputs.required[inputKeysRequired[i]][0][0]; 222 | } 223 | 224 | 225 | } 226 | 227 | 228 | inputArray.push([inputKeysRequired[i],secondV,thirdV]); 229 | }catch(err){ 230 | console.log("error",err) 231 | } 232 | } 233 | 234 | try{ 235 | for (let i = 0; i < inputKeysOptional.length; i++) { 236 | try{ 237 | let thirdV = null; 238 | let secondV = inputs.optional[inputKeysOptional[i]][0]; 239 | if (Array.isArray(secondV)){ 240 | //array 241 | secondV = inputs.optional[inputKeysOptional[i]][0][0]; 242 | thirdV = inputs.optional[inputKeysOptional[i]][0][0]; 243 | } 244 | 245 | let isArr = false 246 | 247 | try { 248 | if( typeof inputs.optional[inputKeysOptional[i]][0] === 'object'){ 249 | isArr = true 250 | } 251 | } 252 | catch(err) { 253 | console.log("error",err) 254 | } 255 | 256 | if (inputs.optional[inputKeysOptional[i]][1]!=undefined && Object.keys(inputs.optional[inputKeysOptional[i]][1]).length > 0){ 257 | 258 | if (!isArr){ 259 | //object 260 | thirdV = inputs.optional[inputKeysOptional[i]][1].default; 261 | } 262 | else{ 263 | thirdV = inputs.optional[inputKeysOptional[i]][0][0]; 264 | } 265 | } 266 | inputArray.push([inputKeysOptional[i],secondV,thirdV]); 267 | }catch(err){ 268 | console.log("error",err) 269 | } 270 | } 271 | 272 | }catch(err){ 273 | console.log(err) 274 | } 275 | 276 | 277 | 278 | 279 | for (let i = 0; i < outputs.length; i++) { 280 | if (outputs_name[i] != undefined) { 281 | outputArray.push([outputs[i],outputs_name[i]]); 282 | } 283 | else{ 284 | outputArray.push(outputs[i],outputs[i]); 285 | } 286 | 287 | 288 | } 289 | 290 | 291 | 292 | let length_loop = outputArray.length; 293 | if (inputArray.length> outputArray.length){ 294 | 295 | length_loop = inputArray.length; 296 | } 297 | for (let i = 0; i < length_loop; i++) { 298 | 299 | const inputList= inputArray[i] ? inputArray[i] : null; 300 | const outputList= outputArray[i] ? outputArray[i] : null; 301 | let inputName = ""; 302 | let inputType = ""; 303 | let inputValue = null; 304 | let outputType = ""; 305 | let outputName = ""; 306 | 307 | if (inputList != null){ 308 | inputName = inputList[0]; 309 | inputType = inputList[1]; 310 | inputValue = inputList[2]; 311 | }else 312 | { 313 | inputName = ""; 314 | inputType = "sb_hidden"; 315 | inputValue = null; 316 | } 317 | if (outputList != null){ 318 | outputType = outputList[0]; 319 | outputName = outputList[1]; 320 | 321 | }else 322 | { 323 | outputType = "sb_hidden"; 324 | outputName = ""; 325 | } 326 | 327 | array_list_type = [ 328 | "BOOLEAN", 329 | "CLIP", 330 | "CLIP_VISION", 331 | "CLIP_VISION_OUTPUT", 332 | "CONDITIONING", 333 | "CONTROL_NET", 334 | "CONTROL_NET_WEIGHTS", 335 | "FLOAT", 336 | "GLIGEN", 337 | "IMAGE", 338 | "IMAGEUPLOAD", 339 | "INT", 340 | "LATENT", 341 | "LATENT_KEYFRAME", 342 | "MASK", 343 | "MODEL", 344 | "SAMPLER", 345 | "SIGMAS", 346 | "STRING", 347 | "STYLE_MODEL", 348 | "T2I_ADAPTER_WEIGHTS", 349 | "TAESD", 350 | "TIMESTEP_KEYFRAME", 351 | "UPSCALE_MODEL", 352 | "VAE", 353 | ] 354 | if (inputValue != null){ 355 | if (inputType == "STRING"){ 356 | last_rows += `
357 |
358 |
${inputName}
359 |
360 |
${inputValue}
361 |
362 |
`; 363 | 364 | }else{ 365 | last_rows += `
366 |
367 |
${inputName}
368 |
369 |
${inputValue}
370 |
371 |
`; 372 | 373 | 374 | } 375 | inputType = "sb_hidden"; 376 | inputName = ""; 377 | } 378 | else{ 379 | if (inputType == "STRING"){ 380 | last_rows += `
381 |
382 |
${inputName}
383 |
384 |
385 |
386 |
`; 387 | } 388 | 389 | } 390 | if (inputType != "STRING"){ 391 | rows += `
392 |
393 |
${inputName}
394 |
395 |
${outputName}
396 |
397 |
`; 398 | 399 | } 400 | 401 | 402 | 403 | } 404 | //console.log( inputArray,outputArray); 405 | 406 | ////create preview 407 | //const nodePreview = document.createElement("div"); 408 | //nodePreview.classList.add("node_preview"); 409 | //nodePreview.id = nodeID; 410 | error = ""; 411 | }catch(err){ 412 | error = "NOT AVAILABLE"; 413 | rows = `` 414 | last_rows = `` 415 | } 416 | return [`
417 |
${nodeID}
418 |
PREVIEW ${error}
419 | ${rows} 420 | ${last_rows} 421 |
`,description]; 422 | } 423 | 424 | 425 | 426 | async function getPreview(item, previewDiv, sidebad_view_width, contentFunction, configFunction = null) { 427 | try { 428 | item.addEventListener('mouseover', async function () { 429 | let shouldShowPreview = true; 430 | 431 | 432 | if (configFunction) { 433 | shouldShowPreview = await configFunction(); 434 | } 435 | 436 | if (shouldShowPreview && this.classList.contains('sidebarItem') && this.tagName === 'LI') { 437 | let descriptionDiv = ""; 438 | const itemPosition = getElementPosition(this); 439 | let previewDivTop = 0; 440 | 441 | // Usa la funzione contentFunction per ottenere il contenuto del preview 442 | const [previewContent, node_description] = await contentFunction(item); 443 | 444 | if (node_description) { 445 | descriptionDiv = "
" + node_description + "
"; 446 | } 447 | 448 | previewDiv.innerHTML = previewContent + descriptionDiv; 449 | previewDiv.style.display = 'block'; 450 | const correction_offset = 45; 451 | let sbtop = calcSBTop(); 452 | 453 | if (itemPosition.top - this.offsetHeight >= 0 && itemPosition.top + previewDiv.offsetHeight < document.body.offsetHeight) { 454 | previewDivTop = itemPosition.top - this.offsetHeight - sbtop; 455 | } else if (itemPosition.top - this.offsetHeight - previewDiv.offsetHeight <= 0) { 456 | previewDivTop = 0 + correction_offset - sbtop; 457 | } else { 458 | previewDivTop = (itemPosition.top + this.offsetHeight) - previewDiv.offsetHeight - sbtop; 459 | } 460 | 461 | let sidebar_width = parseInt(getVar("sidebarWidth")) || 500; 462 | 463 | previewDiv.style.top = `${previewDivTop}px`; 464 | 465 | const previewDivLeft = sidebar_width - sidebad_view_width + correction_offset; 466 | 467 | if (sbPosition == "left") { 468 | previewDiv.style.left = `${previewDivLeft}px`; 469 | } else { 470 | previewDiv.style.right = `${previewDivLeft}px`; 471 | } 472 | } 473 | }); 474 | 475 | item.addEventListener('mouseout', function () { 476 | previewDiv.style.display = 'none'; 477 | }); 478 | } catch (error) { 479 | console.log(error); 480 | } 481 | } 482 | 483 | 484 | 485 | 486 | //change view 487 | function switchTab(tabId) { 488 | if (tabId != null) { 489 | const tabContents = document.querySelectorAll('.content_sidebar'); 490 | const tabHead = document.querySelectorAll('.sidebar-header'); 491 | 492 | let switch_id= tabId.replace("panel", "switch"); 493 | const switchButton = document.getElementById(switch_id); 494 | 495 | if (tabId=="panel_home"){ 496 | const tabIdHead = document.getElementById("sidebar-header"); 497 | tabIdHead.classList.remove('sb_hidden'); 498 | document.getElementById("switch_home").classList.add('sb_button_active'); 499 | 500 | } 501 | else{ 502 | tabHead.forEach(content => { 503 | content.classList.add('sb_hidden'); 504 | 505 | }) 506 | } 507 | // Hide all tabs 508 | tabContents.forEach(content => { 509 | content.classList.add('sb_hidden'); 510 | let switch_id_disbled= content.id.replace("panel", "switch"); 511 | const switchButtonDisbled = document.getElementById(switch_id_disbled); 512 | switchButtonDisbled.classList.remove('sb_button_active'); 513 | }); 514 | 515 | 516 | 517 | // Show the content of the clicked tab 518 | document.getElementById(tabId).classList.remove('sb_hidden'); 519 | switchButton.classList.add('sb_button_active'); 520 | 521 | setVar('sb_current_tab', tabId); 522 | } 523 | } 524 | 525 | 526 | 527 | // PINNED ITEMS 528 | function savePinnedItems(pinnedItems) { 529 | const pinnedItemsString = JSON.stringify(pinnedItems); 530 | //setVar('pinnedItems', pinnedItemsString, 9999); 531 | updateConfiguration('sb_pinnedItems',pinnedItemsString) 532 | } 533 | 534 | 535 | async function loadPinnedItems() { 536 | 537 | const pinnedItemsString = await getConfiguration('sb_pinnedItems');//getVar('pinnedItems'); 538 | 539 | if (pinnedItemsString) { 540 | return JSON.parse(pinnedItemsString); 541 | } 542 | return []; 543 | } 544 | 545 | 546 | function migrationSettings() { 547 | const oldSettingsList = ["pinnedItems","Comfy.Settings.sidebar_noderadius_settings","Comfy.Settings.sidebar_barbottom","Comfy.Settings.sidebar_bartop","Comfy.Settings.sidebar_blur_settings","Comfy.Settings.sidebar_opacity_settings","Comfy.Settings.sidebar_font_settings","Comfy.Settings.sidebar_node_settings"]; 548 | const newSettingsList = ["sb_pinnedItems","sb_noderadius","sb_barbottom","sb_bartop","sb_blur","sb_opacity","sb_font","sb_node"]; 549 | 550 | oldSettingsList.forEach((setting, index) => { 551 | try{ 552 | const newSetting = newSettingsList[index]; 553 | const oldSettingValue = getVar(setting); 554 | if (oldSettingValue) { 555 | if (setting != "pinnedItems") { 556 | updateConfiguration(newSetting, parseFloat(oldSettingValue.replace('"',"").replace('"',""))); 557 | } else { 558 | updateConfiguration(newSetting, oldSettingValue); 559 | } 560 | renameVar(setting, "bk_"+setting) 561 | } 562 | }catch(err){ 563 | console.log(err) 564 | } 565 | }) 566 | // updateConfiguration('sb_pinnedItems',pinnedItemsString) ; 567 | } 568 | 569 | 570 | 571 | function toggleSHSB(force = undefined) { 572 | const side_bars = document.querySelectorAll(".content_sidebar"); 573 | const main_sidebar = document.getElementById('sidebar'); 574 | const search_bar = document.getElementById('searchInputSB'); 575 | const scrollToTopButton = document.getElementById("sb_scrollToTopButton"); 576 | const clearIcon = document.querySelector(".clearIcon"); 577 | const searchCategoryIcon = document.querySelector(".searchCategoryIcon"); 578 | const switch_sidebar = document.getElementById('switch_sidebar'); 579 | const sidebar_views = document.getElementById("sidebar_views"); 580 | 581 | //search_bar.classList.toggle('closed',force); 582 | 583 | side_bars.forEach(side_bar => { 584 | //side_bar.classList.toggle('closed',force); 585 | 586 | if (force !== undefined) { 587 | if (force) { 588 | 589 | setVar("sb_state", "closed"); 590 | side_bar.classList.add('closed'); 591 | clearIcon.classList.add('closed'); 592 | searchCategoryIcon.classList.add('closed'); 593 | search_bar.classList.add('closed'); 594 | scrollToTopButton.classList.add('closed'); 595 | sidebar_views.classList.add('full_rounded'); 596 | 597 | } else { 598 | 599 | setVar("sb_state", "open"); 600 | side_bar.classList.remove('closed'); 601 | clearIcon.classList.remove('closed'); 602 | searchCategoryIcon.classList.remove('closed'); 603 | search_bar.classList.remove('closed'); 604 | scrollToTopButton.classList.remove('closed'); 605 | sidebar_views.classList.remove('full_rounded'); 606 | setVar("sb_minimized", "true"); 607 | } 608 | } else { 609 | 610 | if (side_bar.classList.contains('closed')) { 611 | 612 | setVar("sb_state", "open"); 613 | 614 | side_bar.classList.remove('closed'); 615 | clearIcon.classList.remove('closed'); 616 | searchCategoryIcon.classList.remove('closed'); 617 | search_bar.classList.remove('closed'); 618 | sidebar_views.classList.remove('full_rounded'); 619 | scrollToTopButton.classList.remove('closed'); 620 | //fix for keyboard shortcuts 621 | if (getVar("sb_minimized") == "true") { 622 | switch_sidebar.style.filter = "brightness(0.8)"; 623 | } 624 | 625 | } else { 626 | if (getVar("sb_minimized") == "false") { 627 | 628 | setVar("sb_state", "closed"); 629 | side_bar.classList.add('closed'); 630 | clearIcon.classList.add('closed'); 631 | searchCategoryIcon.classList.add('closed'); 632 | search_bar.classList.add('closed'); 633 | scrollToTopButton.classList.add('closed'); 634 | sidebar_views.classList.add('full_rounded'); 635 | switch_sidebar.style.filter = "brightness(1.0)"; 636 | } else { 637 | switch_sidebar.style.filter = "brightness(0.8)"; 638 | } 639 | } 640 | } 641 | 642 | }); 643 | 644 | 645 | 646 | if (getVar("sb_minimized") == "false") { 647 | 648 | if (force == undefined) { 649 | 650 | setVar("sb_state", "closed"); 651 | setVar("sb_minimized", true); 652 | } 653 | main_sidebar.style.width = '45px'; 654 | } else { 655 | 656 | if (force == undefined) { 657 | 658 | setVar("sb_state", "open"); 659 | setVar("sb_minimized", false); 660 | main_sidebar.style.width = getVar("sidebarWidth") + 'px' || '300px'; 661 | } else if (force == true) { 662 | 663 | 664 | setVar("sb_state", "closed"); 665 | 666 | main_sidebar.style.width = '45px'; 667 | 668 | } else { 669 | 670 | setVar("sb_state", "open"); 671 | main_sidebar.style.width = getVar("sidebarWidth") + 'px' || '300px'; 672 | } 673 | 674 | } 675 | 676 | } 677 | -------------------------------------------------------------------------------- /app/js/functions/utils.js: -------------------------------------------------------------------------------- 1 | 2 | let new_menu_options = []; 3 | let new_submenu_options = []; 4 | let new_menu_options_callback = []; 5 | let new_submenu_options_callback = []; 6 | const settingsStyle = ` 7 | 8 | #comfy-settings-dialog input{ 9 | background: var(--comfy-input-bg); 10 | color: var(--input-text); 11 | border: 1px solid var(--border-color); 12 | padding: 5px; 13 | } 14 | #comfy-settings-dialog tr > td:first-child { 15 | text-align: left; 16 | } 17 | #comfy-settings-dialog select{ 18 | background: var(--bg-color); 19 | color: var(--drag-text); 20 | padding: 5px; 21 | } 22 | 23 | #comfy-settings-dialog { 24 | 25 | background: var(--bg-color); 26 | } 27 | 28 | #comfy-settings-dialog::-webkit-scrollbar { 29 | margin-top: 0.5rem; 30 | height: 1rem; 31 | width: .6rem; 32 | top: 10px; 33 | background-color: transparent; 34 | } 35 | 36 | #comfy-settings-dialog::-webkit-scrollbar:horizontal { 37 | height: .5rem; 38 | width: 2.5rem 39 | } 40 | 41 | #comfy-settings-dialog::-webkit-scrollbar-track { 42 | background-color: var(--comfy-input-bg); 43 | border-radius: 9999px 44 | 45 | } 46 | 47 | #comfy-settings-dialog::-webkit-scrollbar-thumb { 48 | --tw-border-opacity: 1; 49 | background-color: var(--border-color); 50 | border: 0; 51 | border-radius: 9999px; 52 | 53 | } 54 | 55 | #comfy-settings-dialog::-webkit-scrollbar-thumb:hover { 56 | --tw-bg-opacity: 1; 57 | background-color: var(--border-color); 58 | 59 | } 60 | 61 | .comfy-table td, .comfy-table th { 62 | border: 0px solid var(--border-color); 63 | padding: 8px; 64 | } 65 | 66 | 67 | ` 68 | async function api_get(url) { 69 | var response = await api.fetchApi(url, { cache: "no-store" }) 70 | return await response.json() 71 | } 72 | function getDynamicCSSRule(selector, property) { 73 | const styleId = 'dynamic-styles'; 74 | const customStyle = document.getElementById(styleId); 75 | 76 | if (!customStyle) { 77 | return null; 78 | } 79 | 80 | const existingRule = Array.from(customStyle.sheet.cssRules).find(rule => rule.selectorText === selector); 81 | 82 | if (existingRule) { 83 | return existingRule.style.getPropertyValue(property); 84 | } 85 | 86 | return null; 87 | } 88 | 89 | 90 | function addDynamicCSSRule(selector, property, value) { 91 | const styleId = 'dynamic-styles'; 92 | let customStyle = document.getElementById(styleId); 93 | 94 | if (!customStyle) { 95 | customStyle = document.createElement('style'); 96 | customStyle.type = 'text/css'; 97 | customStyle.id = styleId; 98 | document.head.appendChild(customStyle); 99 | } 100 | 101 | const existingRule = Array.from(customStyle.sheet.cssRules).find(rule => rule.selectorText === selector); 102 | if (existingRule) { 103 | existingRule.style.setProperty(property, value, 'important'); 104 | } else { 105 | 106 | customStyle.sheet.insertRule(`${selector} { ${property}: ${value} !important; }`, customStyle.sheet.cssRules.length); 107 | } 108 | } 109 | 110 | function addJavascriptVar(name, value) { 111 | const scripta = document.createElement('script'); 112 | scripta.type = 'text/javascript'; 113 | scripta.innerHTML = "var "+name+" = "+value; 114 | 115 | document.head.appendChild(scripta); 116 | 117 | } 118 | function getVar(name) { 119 | var varValue = localStorage.getItem(name); 120 | if (varValue === null) { 121 | if (name=="pinnedItems") { 122 | //get cookie pinnedItems 123 | var cookies = document.cookie.split('; '); 124 | cookies.forEach(cookie => { 125 | const [cookieName, cookieValue] = cookie.split('='); 126 | if (cookieName === name) { 127 | 128 | setVar(name, cookieValue) 129 | 130 | } 131 | }) 132 | //delete cookie pinnedItems 133 | document.cookie = name + '=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'; 134 | } 135 | else { 136 | return null; 137 | } 138 | 139 | }else { 140 | return varValue; 141 | } 142 | 143 | return null; 144 | } 145 | 146 | function setVar(name, value) { 147 | /*migration to localstorage*/ 148 | localStorage.setItem(name, value); 149 | 150 | } 151 | function removeVar(name) { 152 | localStorage.removeItem(name); 153 | } 154 | 155 | function renameVar(oldName, newName) { 156 | const oldValue = getVar(oldName); 157 | setVar(newName, oldValue); 158 | removeVar(oldName); 159 | 160 | } 161 | 162 | function getElementPosition(element) { 163 | const rect = element.getBoundingClientRect(); 164 | return { 165 | top: rect.top, 166 | left: rect.left 167 | }; 168 | } 169 | 170 | function reverseAndAppend(originalName) { 171 | let words; 172 | let lastWord; 173 | let reversedWords; 174 | let reversedString; 175 | let finalName; 176 | if (originalName.includes(' ')) { 177 | const words = originalName.split(' '); 178 | 179 | lastWord = words.pop(); 180 | 181 | 182 | reversedWords = words.reverse(); 183 | reversedString = reversedWords.join(' '); 184 | 185 | finalName = originalName + ' ' + reversedString; 186 | 187 | return finalName; 188 | 189 | } else { 190 | 191 | 192 | words = originalName.split(/(?=[A-Z])/); 193 | lastWord = words.pop(); 194 | reversedWords = words.reverse(); 195 | reversedString = reversedWords.join(''); 196 | finalName = originalName + reversedString; 197 | 198 | return finalName; 199 | 200 | } 201 | } 202 | function cleanText(text) { 203 | let cleanedText = text.replace(/[^a-zA-Z0-9\s\n\-]/g, ''); 204 | cleanedText = cleanedText.replace(/\s+/g, ' '); 205 | cleanedText = cleanedText.replace(/\n/g, ''); 206 | return cleanedText; 207 | } 208 | 209 | function calcSBTop() { 210 | let sb = document.getElementById('sidebar'); 211 | let sbtop = window.getComputedStyle(sb).getPropertyValue('top').replace('px', ''); 212 | if (getVar('Comfy.Settings.Comfy.UseNewMenu') === '"Top"') { 213 | 214 | return parseInt(sbtop) + 35; 215 | } 216 | else{ 217 | return sbtop; 218 | } 219 | } 220 | //CONTEXT MENU 221 | function createContextMenu(event, subMenus,settingsData) { 222 | event.preventDefault(); 223 | hideAllSubMenus(); 224 | 225 | const sbcontextMenu = document.getElementById('customMenu'); 226 | const menuOptions = document.getElementById('menu-options'); 227 | 228 | // Clear existing menu items 229 | menuOptions.innerHTML = ''; 230 | 231 | // Populate main options 232 | settingsData.menuctx_options.forEach((option, index) => { 233 | const li = document.createElement('li'); 234 | li.textContent = option; 235 | li.addEventListener('mouseenter', () => { 236 | hideAllSubMenus(); 237 | showSubMenu(subMenus[index], li); 238 | }); 239 | try { 240 | 241 | 242 | if (settingsData.menuctx_opt_callback[index].indexOf(".") !== -1) { 243 | 244 | // Handle sub menu option 245 | var mainfunc = settingsData.menuctx_opt_callback[index].split(".")[0]; 246 | var subfunc = settingsData.menuctx_opt_callback[index].split(".")[1]; 247 | if (typeof (window[mainfunc][subfunc]) === 'function') { 248 | 249 | 250 | li.addEventListener('click', () => { 251 | // Handle sub menu click 252 | window[mainfunc][subfunc](event); 253 | }); 254 | 255 | } 256 | } 257 | else{ 258 | 259 | if (typeof (window[settingsData.menuctx_opt_callback[index]]) === 'function') { 260 | li.addEventListener('click', () => { 261 | // Handle main option click 262 | window[settingsData.menuctx_opt_callback[index]](event); 263 | 264 | }); 265 | } 266 | } 267 | } catch (error) { 268 | console.log(error); 269 | 270 | } 271 | menuOptions.appendChild(li); 272 | }); 273 | 274 | // Show context menu 275 | sbcontextMenu.style.display = 'block'; 276 | let sbtop = calcSBTop(); 277 | 278 | sbcontextMenu.style.top = (event.clientY - sbtop) + 'px'; 279 | if (sbPosition === "left") { 280 | 281 | sbcontextMenu.style.left = event.clientX + 'px'; 282 | } else { 283 | // const sidebar_width = getVar("sidebarWidth"); 284 | //console.log(parseInt(sidebar_width)) 285 | //console.log(event) 286 | //console.log(event.clientX - parseInt(sidebar_width) ) 287 | //temp bugfix 288 | sbcontextMenu.style.left = event.layerX + 'px'; 289 | } 290 | 291 | // Hide context menu on body click 292 | document.body.addEventListener('click', hideContextMenu); 293 | } 294 | 295 | function showSubMenu(subMenu, element) { 296 | try{ 297 | if (subMenu) { 298 | subMenu.style.display = 'block'; 299 | 300 | const rect = element.getBoundingClientRect(); 301 | subMenu.style.top = element.offsetTop + 'px'; 302 | 303 | subMenuVisible = true; 304 | } 305 | } catch (error) { 306 | console.log(error); 307 | } 308 | } 309 | 310 | function hideAllSubMenus() { 311 | const subMenus = document.querySelectorAll('.sub-menu'); 312 | subMenus.forEach(subMenu => { 313 | subMenu.style.display = 'none'; 314 | }); 315 | subMenuVisible = false; 316 | } 317 | 318 | function hideContextMenu() { 319 | const sbcontextMenu = document.getElementById('customMenu'); 320 | sbcontextMenu.style.display = 'none'; 321 | document.body.removeEventListener('click', hideContextMenu); 322 | } 323 | 324 | let subMenuVisible = false; 325 | let lastTargetClicked = null; 326 | 327 | async function setContextMenu(settingsData,class_menu_item,main=0,exluded_class=null,included_class = null) { 328 | 329 | 330 | if (main==1){ 331 | settingsData.menuctx_options = settingsData.menuctx_options.concat(new_menu_options); 332 | settingsData.menuctx_subOptions = settingsData.menuctx_subOptions.concat(new_submenu_options); 333 | settingsData.menuctx_opt_callback = settingsData.menuctx_opt_callback.concat(new_menu_options_callback); 334 | settingsData.menuctx_sub_opt_callback = settingsData.menuctx_sub_opt_callback.concat(new_submenu_options_callback); 335 | } 336 | const sbcontextMenu = document.getElementById('customMenu'); 337 | const menuOptions = document.createElement("ul"); 338 | menuOptions.id = "menu-options"; 339 | sbcontextMenu.appendChild(menuOptions); 340 | 341 | const subMenus = []; 342 | for (let i = 0; i < settingsData.menuctx_subOptions.length; i++) { 343 | 344 | 345 | 346 | const subMenu = document.createElement("ul"); 347 | subMenu.id = `sub-menu-op${i + 1}`; 348 | subMenu.classList.add("sub-menu"); 349 | if (settingsData.menuctx_subOptions[i].length == 0) { 350 | subMenu.classList.add("cat_empty"); 351 | } 352 | 353 | sbcontextMenu.appendChild(subMenu); 354 | subMenus.push(subMenu); 355 | 356 | for (let j = 0; j < settingsData.menuctx_subOptions[i].length; j++) { 357 | const li = document.createElement("li"); 358 | li.textContent = settingsData.menuctx_subOptions[i][j]; 359 | subMenu.appendChild(li); 360 | try { 361 | 362 | if (settingsData.menuctx_sub_opt_callback[i][j].indexOf(".") !== -1) { 363 | 364 | // Handle sub menu option 365 | var mainfunc = settingsData.menuctx_sub_opt_callback[i][j].split(".")[0]; 366 | var subfunc = settingsData.menuctx_sub_opt_callback[i][j].split(".")[1]; 367 | if (typeof (window[mainfunc][subfunc]) === 'function') { 368 | 369 | 370 | li.addEventListener('click', () => { 371 | // Handle sub menu click 372 | 373 | window[mainfunc][subfunc](lastTargetClicked,settingsData.menuctx_subOptions[i][j]); 374 | 375 | }); 376 | 377 | } 378 | } 379 | else{ 380 | 381 | if (typeof (window[settingsData.menuctx_sub_opt_callback[i][j]]) === 'function') { 382 | li.addEventListener('click', () => { 383 | // Handle main option click 384 | 385 | 386 | window[settingsData.menuctx_sub_opt_callback[i][j]](lastTargetClicked,settingsData.menuctx_subOptions[i][j]); 387 | 388 | }); 389 | } 390 | } 391 | 392 | 393 | 394 | 395 | 396 | 397 | } catch (error) { 398 | console.log(error); 399 | } 400 | } 401 | 402 | } 403 | 404 | const sidebarItems = document.querySelectorAll(class_menu_item); 405 | //const sb =document.getElementById('panel_home'); 406 | 407 | 408 | for (const sidebarItem of sidebarItems) { 409 | sidebarItem.addEventListener('contextmenu', (event) => { 410 | 411 | if(!event.target.classList.contains(exluded_class)){ 412 | 413 | if (included_class != null && !event.target.classList.contains(included_class)){ 414 | return; 415 | } 416 | createContextMenu (event, subMenus,settingsData); 417 | lastTargetClicked = event; 418 | } 419 | }); 420 | } 421 | 422 | //ContextMenu 423 | //sb.addEventListener("contextmenu", function(event) { 424 | // event.preventDefault(); 425 | // 426 | // const customMenu = document.getElementById("customMenu"); 427 | // customMenu.style.display = "block"; 428 | // customMenu.style.left = event.pageX + "px"; 429 | // customMenu.style.top = event.pageY + "px"; 430 | //}); 431 | // 432 | window.addEventListener("click", function(event) { 433 | const customMenu = document.getElementById("customMenu"); 434 | customMenu.style.display = "none"; 435 | }); 436 | } 437 | 438 | 439 | // Function to check for special characters 440 | function containsSpecialCharacters(name, level=0) { 441 | // Define a regular expression pattern to match special characters 442 | if (level == 0) { 443 | var specialCharacters = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/; 444 | } else if (level == 1) { 445 | var specialCharacters = /[\*':"\\|,<>\/?]/g; 446 | } else{ 447 | var specialCharacters = /['"]/gi; 448 | } 449 | 450 | // Return true if the name contains any special characters, false otherwise 451 | return specialCharacters.test(name); 452 | } 453 | 454 | async function reloadCtxMenu() { 455 | //load folder name 456 | var nameRequest = await fetch('sidebar/current'); 457 | var nameFolder = await nameRequest.json(); 458 | 459 | const cnPath = `../extensions/${nameFolder}/` 460 | 461 | const response3 = await fetch(cnPath +'settings.json'); 462 | const settingsData = await response3.json(); 463 | 464 | setContextMenu(settingsData,"#panel_home .sidebarItem",1); 465 | 466 | } 467 | 468 | function setNodeStatus(varname,node,status) { 469 | try{//'sb_categoryNodeStatus' 470 | const categoryNodeStatus = JSON.parse(localStorage.getItem(varname)) || {}; 471 | categoryNodeStatus[node] = [status]; 472 | localStorage.setItem(varname, JSON.stringify(categoryNodeStatus)); 473 | }catch(err){ } 474 | } 475 | 476 | function getNodeStatus(varname,node) { 477 | try{ 478 | const categoryNodeStatus = JSON.parse(localStorage.getItem(varname)) || {}; 479 | return categoryNodeStatus[node] || "none"; 480 | }catch(err){ } 481 | return "none"; 482 | } 483 | 484 | function getNodesStatus() { 485 | let categoryNodeStatus = JSON.parse(localStorage.getItem(varname)) || {}; 486 | return categoryNodeStatus; 487 | } 488 | 489 | 490 | // MODAL 491 | 492 | function setModalContext(addon=null) { 493 | if (addon==null) { 494 | return ` 495 |

Add New Category

496 |
497 | 498 | 499 |
500 | 501 | `; 502 | } 503 | else { 504 | return addon 505 | } 506 | } 507 | 508 | function openModal(addon=null) { 509 | // Create HTML elements for the modal 510 | const modalBackdrop = document.createElement('div'); 511 | modalBackdrop.classList.add('sb-modal-backdrop'); 512 | 513 | const modal = document.createElement('div'); 514 | modal.classList.add('sb-category-dialog'); 515 | modal.innerHTML = setModalContext(addon) 516 | 517 | // Add the modal to the DOM 518 | document.body.appendChild(modalBackdrop); 519 | document.body.appendChild(modal); 520 | 521 | // Add an event listener for the close button 522 | const closeModalButton = modal.querySelector('#closeModalButton'); 523 | closeModalButton.addEventListener('click', closeModal); 524 | try{ 525 | // Focus the input field 526 | inputElement = document.getElementById('sb-categoryName'); 527 | inputElement.focus(); 528 | const length = inputElement.value.length; 529 | inputElement.setSelectionRange(length, length); 530 | 531 | }catch(err){ 532 | 533 | } 534 | } 535 | 536 | 537 | 538 | // Function to close the modal 539 | function closeModal() { 540 | const modalBackdrop = document.querySelector('.sb-modal-backdrop'); 541 | const modal = document.querySelector('.sb-category-dialog'); 542 | try{ 543 | // Remove the modal from the DOM 544 | modalBackdrop.parentNode.removeChild(modalBackdrop); 545 | modal.parentNode.removeChild(modal); 546 | } 547 | catch(err){ 548 | console.log(err); 549 | } 550 | } 551 | 552 | 553 | function rgbToHex(rgb) { 554 | // Verifica se il colore è nel formato "rgb()" 555 | if (rgb.indexOf('rgb') !== -1) { 556 | // Esegue il parsing dei valori di rosso, verde e blu 557 | const [r, g, b] = rgb.match(/\d+/g); 558 | 559 | // Converte i valori in esadecimale e li concatena con "#" 560 | return `#${parseInt(r, 10).toString(16).padStart(2, '0')}${parseInt(g, 10).toString(16).padStart(2, '0')}${parseInt(b, 10).toString(16).padStart(2, '0')}`; 561 | } 562 | return rgb; // Se il colore è già nel formato con "#", lo restituisce direttamente 563 | } 564 | 565 | 566 | function hexToRgb(hex) { 567 | try{ 568 | // Rimuovi il carattere # se presente 569 | hex = hex.replace(/^#/, ''); 570 | 571 | // Estrai i valori R, G e B 572 | let r = parseInt(hex.substring(0, 2), 16); 573 | let g = parseInt(hex.substring(2, 4), 16); 574 | let b = parseInt(hex.substring(4, 6), 16); 575 | 576 | // Restituisci il colore in formato RGB 577 | return `${r}, ${g}, ${b}`; 578 | }catch(err){ 579 | console.log(err) 580 | return "#000000"; 581 | } 582 | } 583 | 584 | //click anywhere outside the modal to close it 585 | document.body.addEventListener('click', function(event) { 586 | const modalBackdrop = document.querySelector('.sb-modal-backdrop'); 587 | const modal = document.querySelector('.sb-category-dialog'); 588 | if (event.target === modalBackdrop) { 589 | closeModal(); 590 | } 591 | }) 592 | 593 | 594 | 595 | 596 | 597 | function showSettings() { 598 | const sb_settingsDiv = document.getElementById("sb_settingsDiv"); 599 | const sb_modal_backdrop = document.getElementById("sb-modal-backdrop-settings"); 600 | sb_settingsDiv.classList.remove('sb_hidden'); 601 | sb_modal_backdrop.classList.remove('sb_hidden'); 602 | } 603 | 604 | 605 | 606 | 607 | function showIESettings(operation) { 608 | const sb_settingsDiv = document.getElementById("sb_settingsDiv_"+operation); 609 | sb_settingsDiv.classList.remove('sb_hidden'); 610 | 611 | } 612 | 613 | function isBottomEdgeVisible(el) { 614 | 615 | var rect = el.getBoundingClientRect(); 616 | var viewportHeight = window.innerHeight || document.documentElement.clientHeight; 617 | 618 | if (rect.bottom <= viewportHeight) { 619 | return true; 620 | } 621 | 622 | return false; 623 | } 624 | 625 | function convertCanvasToOffset(canvas, pos, out) { 626 | out = out || [0, 0]; 627 | out[0] = pos[0] / canvas.scale - canvas.offset[0]; 628 | out[1] = pos[1] / canvas.scale - canvas.offset[1]; 629 | return out; 630 | }; 631 | -------------------------------------------------------------------------------- /app/panels/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/app/panels/.DS_Store -------------------------------------------------------------------------------- /app/panels/assets_downloader/assets_downloader.html: -------------------------------------------------------------------------------- 1 |
2 |
Explore
3 |
My Library
4 |
5 |
6 |
7 | 111 |
112 | 113 |
114 |
115 | 116 |
117 |
118 |
119 | 120 |
121 |
122 |
123 | 192 |
193 |
194 |
195 |
-------------------------------------------------------------------------------- /app/panels/assets_downloader/style.css: -------------------------------------------------------------------------------- 1 | #assets_downloader_main,#assets_downloader_header,.sb-mymodels { 2 | 3 | justify-content: center; 4 | align-items: center; 5 | height: 44px; 6 | margin: 0; 7 | } 8 | #assets_downloader_main, .sb-mymodels { 9 | height: auto; 10 | padding-top: 0px; 11 | padding-bottom: 150px; 12 | } 13 | #assets_downloader_header { 14 | margin: 10px 0px; 15 | font-size: 16px; 16 | 17 | } 18 | /* General styles for the header */ 19 | #assets_downloader_header { 20 | display: flex; 21 | align-items: center; 22 | padding: 10px; 23 | 24 | } 25 | 26 | #assets_downloader_header .h-header { 27 | display: flex; 28 | align-items: center; 29 | flex: 1; 30 | position: relative; 31 | width: 100%; 32 | flex-direction: column; 33 | } 34 | 35 | .n-sb-searchmodels { 36 | box-sizing: border-box; 37 | width: 100%; 38 | border-radius: 5px; 39 | padding: 10px; 40 | border: none; 41 | user-select: none; 42 | background: var(--comfy-input-bg); 43 | color: var(--input-text); 44 | line-height: 1.4; 45 | border: 1px solid var(--border-color); 46 | 47 | 48 | 49 | padding: 10px; 50 | font-size: 16px; 51 | border-radius: 4px; 52 | margin-right: 10px; 53 | } 54 | 55 | #assets_downloader_header select { 56 | padding: 10px; 57 | font-size: 16px; 58 | width : 145px; 59 | border-radius: 4px; 60 | 61 | border-color: var(--comfy-input-bg); 62 | background: var(--comfy-input-bg); 63 | } 64 | 65 | 66 | 67 | /* Main container styles */ 68 | #assets_downloader_main { 69 | padding: 4px; 70 | } 71 | 72 | /* Styles for the results container */ 73 | #results-container { 74 | display: flex; 75 | justify-content: center; /* Center horizontally */ 76 | align-items: center; /* Center vertically */ 77 | height: 100%; /* Ensure the container has defined height */ 78 | min-height: 200px; /* Minimum height for the container, adjust if needed */ 79 | position: relative; 80 | } 81 | 82 | /* Styles for the loading icon */ 83 | .pi-spinner-dotted { 84 | font-size: 2rem; /* Size of the loading icon */ 85 | } 86 | 87 | 88 | .result-container,#library-container { 89 | display: flex; 90 | flex-wrap: wrap; 91 | gap: 10px; 92 | justify-content: center; 93 | 94 | } 95 | .result-card { 96 | background-color: var(--bg-color); 97 | border-radius: 8px; 98 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 99 | width: 287px; 100 | padding: 8px; 101 | height: 372px; 102 | text-align: center; 103 | position: relative; 104 | 105 | } 106 | .result-card img, 107 | .result-card video { 108 | max-width: 100%; 109 | min-width: 100%; 110 | height: 60%; 111 | width: 100%; 112 | border-radius: 8px; 113 | /* height: auto; */ 114 | object-fit: cover; 115 | object-position: center center; 116 | transition: transform 400ms; 117 | 118 | } 119 | .result-card h3 { 120 | margin: 10px 0; 121 | font-size: 18px; 122 | } 123 | .result-card p { 124 | color: #555; 125 | margin: 5px 0; 126 | } 127 | .result-card .username { 128 | font-weight: bold; 129 | color: #007bff; 130 | } 131 | .download-button { 132 | display: inline-block; 133 | margin-top: 10px; 134 | padding: 10px 20px; 135 | background-color: #007bff; 136 | color: #fff; 137 | text-decoration: none; 138 | border-radius: 5px; 139 | font-size: 16px; 140 | cursor: pointer; 141 | } 142 | .download-button:hover { 143 | background-color: #0056b3; 144 | } 145 | 146 | 147 | /* multiselect from: https://codeshack.io/multi-select-dropdown-html-javascript/ */ 148 | .multi-select { 149 | display: flex; 150 | box-sizing: border-box; 151 | flex-direction: column; 152 | position: relative; 153 | width: 100%; 154 | user-select: none; 155 | 156 | } 157 | .multi-select .multi-select-header { 158 | border: 1px solid #dee2e6; 159 | padding: 7px 30px 7px 12px; 160 | overflow: hidden; 161 | gap: 7px; 162 | min-height: 43px; 163 | } 164 | .multi-select .multi-select-header::after { 165 | content: ""; 166 | display: block; 167 | position: absolute; 168 | top: 50%; 169 | right: 15px; 170 | transform: translateY(-50%); 171 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23949ba3' viewBox='0 0 16 16'%3E%3Cpath d='M8 13.1l-8-8 2.1-2.2 5.9 5.9 5.9-5.9 2.1 2.2z'/%3E%3C/svg%3E"); 172 | height: 12px; 173 | width: 12px; 174 | } 175 | .multi-select .multi-select-header.multi-select-header-active { 176 | border-color: var(--comfy-input-bg); 177 | background: var(--comfy-input-bg); 178 | } 179 | .multi-select .multi-select-header.multi-select-header-active::after { 180 | transform: translateY(-50%) rotate(180deg); 181 | } 182 | .multi-select .multi-select-header.multi-select-header-active + .multi-select-options { 183 | display: flex; 184 | } 185 | .multi-select .multi-select-header .multi-select-header-placeholder { 186 | color: var(--content-fg); 187 | } 188 | .multi-select .multi-select-header .multi-select-header-option { 189 | display: inline-flex; 190 | align-items: center; 191 | background-color: var(--comfy-menu-bg); 192 | font-size: 14px; 193 | padding: 3px 8px; 194 | border-radius: 4px; 195 | text-wrap: nowrap; 196 | } 197 | .multi-select .multi-select-header .multi-select-header-max { 198 | font-size: 14px; 199 | color: var(--content-fg); 200 | } 201 | .multi-select .multi-select-options { 202 | display: none; 203 | box-sizing: border-box; 204 | flex-flow: wrap; 205 | position: absolute; 206 | top: 100%; 207 | /* left: 0; */ 208 | right: 0; 209 | z-index: 999; 210 | margin-top: 5px; 211 | /* padding: 5px; */ 212 | background-color: var(--bg-color); 213 | border-radius: 5px; 214 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); 215 | /* max-height: 200px; */ 216 | overflow-y: auto; 217 | overflow-x: hidden; 218 | width: fit-content; 219 | } 220 | .multi-select .multi-select-options::-webkit-scrollbar { 221 | width: 5px; 222 | } 223 | .multi-select .multi-select-options::-webkit-scrollbar-track { 224 | background: #f0f1f3; 225 | } 226 | .multi-select .multi-select-options::-webkit-scrollbar-thumb { 227 | background: #cdcfd1; 228 | } 229 | .multi-select .multi-select-options::-webkit-scrollbar-thumb:hover { 230 | background: #b2b6b9; 231 | } 232 | .multi-select .multi-select-options .multi-select-option, .multi-select .multi-select-options .multi-select-all { 233 | padding: 4px 12px; 234 | height: 42px; 235 | } 236 | .multi-select .multi-select-options .multi-select-option .multi-select-option-radio, .multi-select .multi-select-options .multi-select-all .multi-select-option-radio { 237 | margin-right: 14px; 238 | height: 16px; 239 | width: 16px; 240 | border: 1px solid #ced4da; 241 | border-radius: 4px; 242 | } 243 | .multi-select .multi-select-options .multi-select-option .multi-select-option-text, .multi-select .multi-select-options .multi-select-all .multi-select-option-text { 244 | box-sizing: border-box; 245 | flex: 1; 246 | overflow: hidden; 247 | text-overflow: ellipsis; 248 | white-space: nowrap; 249 | color: inherit; 250 | font-size: 16px; 251 | line-height: 20px; 252 | } 253 | .multi-select .multi-select-options .multi-select-option.multi-select-selected .multi-select-option-radio, .multi-select .multi-select-options .multi-select-all.multi-select-selected .multi-select-option-radio { 254 | border-color: var(--border-color); 255 | background-color: var(--bg-color); 256 | } 257 | .multi-select .multi-select-options .multi-select-option.multi-select-selected .multi-select-option-radio::after, .multi-select .multi-select-options .multi-select-all.multi-select-selected .multi-select-option-radio::after { 258 | content: ""; 259 | display: block; 260 | width: 3px; 261 | height: 7px; 262 | margin: 0.12em 0 0 0.27em; 263 | border: solid #fff; 264 | border-width: 0 0.15em 0.15em 0; 265 | transform: rotate(45deg); 266 | } 267 | .multi-select .multi-select-options .multi-select-option.multi-select-selected .multi-select-option-text, .multi-select .multi-select-options .multi-select-all.multi-select-selected .multi-select-option-text { 268 | color: var(--content-fg); 269 | } 270 | .multi-select .multi-select-options .multi-select-option:hover, .multi-select .multi-select-options .multi-select-option:active, .multi-select .multi-select-options .multi-select-all:hover, .multi-select .multi-select-options .multi-select-all:active { 271 | background-color: var(--content-bg); 272 | border-radius: 0; 273 | } 274 | .multi-select .multi-select-options .multi-select-all { 275 | border-bottom: 1px solid #f1f3f5; 276 | border-radius: 0; 277 | } 278 | .multi-select .multi-select-options .multi-select-search { 279 | padding: 7px 10px; 280 | border: 1px solid #dee2e6; 281 | border-radius: 5px; 282 | margin: 10px 10px 5px 10px; 283 | width: 100%; 284 | outline: none; 285 | font-size: 16px; 286 | } 287 | .multi-select .multi-select-options .multi-select-search::placeholder { 288 | color: #b2b5b9; 289 | } 290 | .multi-select .multi-select-header, .multi-select .multi-select-option, .multi-select .multi-select-all { 291 | display: flex; 292 | flex-wrap: wrap; 293 | box-sizing: border-box; 294 | align-items: center; 295 | border-radius: 5px; 296 | cursor: pointer; 297 | display: flex; 298 | align-items: center; 299 | width: 100%; 300 | font-size: 16px; 301 | color: var(--content-fg); 302 | border-color: var(--comfy-input-bg); 303 | background: var(--comfy-input-bg); 304 | min-width: max-content; 305 | } 306 | 307 | 308 | #baseModelFilter{ 309 | width: 157px !important; 310 | } 311 | #t1, #t2{ 312 | display: none; 313 | } 314 | 315 | 316 | #assets_downloader_header { 317 | display: flex; 318 | flex: 1; 319 | position: relative; 320 | width: calc(100% - 5px); 321 | flex-direction: column; 322 | justify-content: space-between; 323 | align-items: center; 324 | height: auto; 325 | margin-top: 10px; 326 | } 327 | 328 | #assets_downloader_header input:focus, #assets_downloader_header select:focus, .multi-select .multi-select-header:active { 329 | outline: var(--border-color) solid 2px; 330 | } 331 | #assets_downloader_main a, .sb-mymodels a { 332 | text-decoration: none; 333 | color: #fff; 334 | } 335 | .search-row { 336 | width: 100%; 337 | display: flex; 338 | justify-content: center; 339 | } 340 | 341 | .n-sb-searchmodels { 342 | padding: 10px; 343 | font-size: 16px; 344 | border-radius: 4px; 345 | width: 100%; /* Usa tutta la larghezza disponibile nella riga */ 346 | max-width: 100%; /* Nessun limite alla larghezza massima */ 347 | } 348 | 349 | .type-checkpoint { 350 | position: absolute; 351 | top: 0; 352 | right: 0px; 353 | padding: 5px; 354 | background: var(--content-fg); 355 | border-radius: 5px; 356 | margin: 10px 10px 0px 0px !important; 357 | opacity: 0.7; 358 | text-transform: uppercase; 359 | font-weight: bold; 360 | font-size: smaller; 361 | } 362 | #assets_downloader_main .stats, .sb-mymodels .stats { 363 | position: absolute; 364 | bottom: 0; 365 | background: #404040; 366 | padding: 5px; 367 | border-radius: 5px; 368 | color: var(--content-fg); 369 | right: 3px; 370 | } 371 | 372 | #assets_downloader_main .username, .sb-mymodels .username { 373 | position: absolute; 374 | bottom: 0px; 375 | right: 0; 376 | margin: 5px; 377 | background: var(--comfy-menu-bg); 378 | padding: 6px; 379 | border-radius: 5px; 380 | } 381 | /* Filtri */ 382 | .filters-row { 383 | width: 100%; 384 | max-width: 600px; 385 | display: flex; 386 | flex-wrap: wrap; 387 | justify-content: center; 388 | gap: 3px; 389 | margin-right: 15px; 390 | } 391 | 392 | #t1 .filters-row > * { 393 | flex: 1 1 22%; 394 | /* min-width: 120px; */ 395 | } 396 | /* Stili per le selezioni */ 397 | #assets_downloader_header select { 398 | padding: 10px; 399 | font-size: 16px; 400 | border-radius: 4px; 401 | width: 100%; 402 | max-width: 150px; 403 | height: 42px; 404 | margin-top: 10px; 405 | } 406 | 407 | /* Multi-select */ 408 | #assets_downloader_header .multi-select { 409 | width: 145px; /* Larghezza massima per il multi-select */ 410 | margin-top: 10px; 411 | } 412 | 413 | /* Adattamento responsive */ 414 | @media (max-width: 768px) { 415 | .filters-row { 416 | flex-direction: column; /* I filtri si dispongono in colonna su schermi più piccoli */ 417 | align-items: center; /* Centratura orizzontale dei filtri su schermi più piccoli */ 418 | } 419 | 420 | select, .multi-select,.n-sb-searchmodels { 421 | width: 100%; /* Usa tutta la larghezza disponibile */ 422 | max-width: 100%; /* Nessun limite alla larghezza massima */ 423 | } 424 | } 425 | 426 | 427 | .sb-kebab-menu { 428 | position: relative; 429 | display: inline-block; 430 | color: var(--content-fg); 431 | margin-right: 10px; 432 | display: flex; 433 | align-items: center; 434 | } 435 | 436 | .sb-kebab-icon { 437 | font-size: 24px; 438 | cursor: pointer; 439 | } 440 | 441 | .sb-menu { 442 | display: none; 443 | position: absolute; 444 | right: 0; 445 | background-color:var(--bg-color); 446 | box-shadow: 0px 8px 16px rgba(0,0,0,0.2); 447 | list-style-type: none; 448 | padding: 10px 0; 449 | margin: 0; 450 | border-radius: 4px; 451 | z-index: 1; 452 | width: max-content !important; 453 | } 454 | 455 | .sb-menu li { 456 | padding: 8px 16px; 457 | cursor: pointer; 458 | } 459 | 460 | .sb-menu li:hover { 461 | background-color: var(--comfy-menu-bg); 462 | } 463 | 464 | 465 | .sb-rebuild-bg-pgs { 466 | text-align: left; 467 | width: inherit; 468 | padding-right: 28px; 469 | display: none; 470 | } 471 | .sb-rebuild-bg-pgs label { 472 | font-size: 0.6rem; 473 | line-height: 1.6; 474 | position: absolute; 475 | padding-left: 5px; 476 | } 477 | .sb-rebuild-pgs { 478 | width: 0%; 479 | background: #1da874a6; 480 | border-radius: 1px; 481 | height: 16px; 482 | font-size: 0.7vh; 483 | text-align: center; 484 | animation: pulsate 1.5s ease-in-out infinite; 485 | } 486 | 487 | @keyframes pulsate { 488 | 0% { 489 | background-color: #1da874a6; /* Colore iniziale */ 490 | } 491 | 50% { 492 | background-color: #28a745; /* Colore più brillante o diverso */ 493 | } 494 | 100% { 495 | background-color: #1da874a6; /* Torna al colore iniziale */ 496 | } 497 | } 498 | 499 | #assets_downloader_main .card, .sb-mymodels .card { 500 | position: relative; 501 | width: 220px; 502 | height: 340px; 503 | overflow: hidden; 504 | border-radius: 10px; 505 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 506 | background-size: cover; 507 | background-position: center; 508 | /*box-shadow: 0 0 7px 0px rgb(170 133 32 / 70%); */ 509 | cursor: pointer; 510 | } 511 | 512 | #assets_downloader_main .card img , .sb-mymodels .card img { 513 | position: absolute; 514 | width: 100%; 515 | height: 100%; 516 | object-fit: cover; 517 | z-index: 1; 518 | } 519 | 520 | #assets_downloader_main .header,#assets_downloader_main .footer , .sb-mymodels .header, .sb-mymodels .footer { 521 | position: absolute; 522 | width: 100%; 523 | left: 0; 524 | z-index: 2; 525 | text-align: left; 526 | color: white; 527 | padding: 10px; 528 | } 529 | 530 | #assets_downloader_main .header, .sb-mymodels .header { 531 | top: 0; 532 | text-align: left; 533 | width: -moz-available; 534 | width: -webkit-fill-available; 535 | 536 | } 537 | 538 | #assets_downloader_main .footer, .sb-mymodels .footer { 539 | bottom: 0; 540 | 541 | } 542 | 543 | #assets_downloader_main .content, .sb-mymodels .content { 544 | /*position: absolute; 545 | top: 50%; 546 | left: 50%; 547 | transform: translate(-50%, -50%); 548 | z-index: 2; 549 | text-align: center;*/ 550 | color: white; 551 | } 552 | #assets_downloader_main .card .header .main-stripe, .sb-mymodels .card .header .main-stripe { 553 | box-sizing: border-box; 554 | -webkit-box-flex: 0; 555 | 556 | } 557 | 558 | #assets_downloader_main .card .header .sub-stripe, .sb-mymodels .card .header .sub-stripe, #assets_downloader_main .card .footer .sub-stripe, .sb-mymodels .card .footer .sub-stripe { 559 | display: inline-flex; 560 | -webkit-box-align: center; 561 | align-items: center; 562 | -webkit-box-pack: center; 563 | justify-content: center; 564 | width: auto; 565 | text-transform: uppercase; 566 | font-weight: 700; 567 | letter-spacing: 0.25px; 568 | cursor: inherit; 569 | text-overflow: ellipsis; 570 | overflow: hidden; 571 | background: rgba(0, 0, 0, 0.51); 572 | border: 1px solid transparent; 573 | color: rgb(254, 254, 254); 574 | border-radius: 32px; 575 | height: 26px; 576 | font-size: 9px; 577 | line-height: 18px; 578 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; 579 | text-decoration: none; 580 | padding: 0px 10.6667px; 581 | box-sizing: border-box; 582 | } 583 | #assets_downloader_main .card .header .main-stripe .separator, .sb-mymodels .card .header .sub-stripe .separator { 584 | margin: -4px 8px; 585 | border-left-color: rgba(255, 255, 255, 0.31); 586 | border-right: 1px solid rgba(0, 0, 0, 0.2); 587 | border-left-width: 1px; 588 | border-left-style: solid; 589 | } 590 | #assets_downloader_main .card .card-main-text, .sb-mymodels .card .card-main-text { 591 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; 592 | -webkit-tap-highlight-color: transparent; 593 | overflow: hidden; 594 | text-overflow: ellipsis; 595 | display: -webkit-box; 596 | -webkit-line-clamp: 3; 597 | -webkit-box-orient: vertical; 598 | color: inherit; 599 | font-size: 1.5vh; 600 | text-decoration: none; 601 | font-weight: 700; 602 | filter: drop-shadow(rgba(0, 0, 0, 0.8) 1px 1px 1px); 603 | line-height: 1.2; 604 | margin-bottom: 5px; 605 | } 606 | 607 | #assets_downloader_main .stats-section, .sb-mymodels .stats-section{ 608 | -webkit-tap-highlight-color: transparent; 609 | 610 | font-size: 11px; 611 | line-height: 18px; 612 | text-decoration: none; 613 | padding: 0px 10.6667px; 614 | box-sizing: border-box; 615 | display: inline-flex; 616 | -webkit-box-align: center; 617 | align-items: center; 618 | width: auto; 619 | font-weight: 700; 620 | letter-spacing: 0.25px; 621 | cursor: inherit; 622 | text-overflow: ellipsis; 623 | overflow: hidden; 624 | background: rgb(0 0 0 / 45%); 625 | border: 1px solid transparent; 626 | align-self: flex-start; 627 | color: rgb(254, 254, 254); 628 | border-radius: 32px; 629 | height: 26px; 630 | } 631 | #assets_downloader_main .card .creator-name, .sb-mymodels .card .creator-name{ 632 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; 633 | -webkit-tap-highlight-color: transparent; 634 | overflow: hidden; 635 | text-overflow: ellipsis; 636 | display: -webkit-box; 637 | -webkit-line-clamp: 1; 638 | -webkit-box-orient: vertical; 639 | color: #ffffff; 640 | font-size: 0.70vw; 641 | line-height: 1.55; 642 | /* text-decoration: none; */ 643 | font-weight: 500; 644 | /* background-clip: text; */ 645 | /* -webkit-text-fill-color: transparent; */ 646 | vertical-align: middle; 647 | filter: drop-shadow(rgba(0, 0, 0, 0.8) 3px 1px 0px); 648 | text-align: left; 649 | padding: 0; 650 | margin: 0px; 651 | 652 | } 653 | .card .model-image { 654 | transition: transform 0.3s ease-in-out; 655 | } 656 | /* .card img:hover, .image-overlay:hover{ 657 | transform: scale(1.05); 658 | }*/ 659 | .card .stat-item i { 660 | padding: 0 5px; 661 | } 662 | 663 | /* Overlay con effetto vetro */ 664 | .card .image-overlay { 665 | position: absolute; 666 | top: 0; 667 | left: 0; 668 | width: 100%; 669 | height: 100%; 670 | background: linear-gradient(to bottom, rgb(14 14 14 / 0%) 0%, rgb(47 47 58 / 79%) 100%); 671 | 672 | display: flex; 673 | justify-content: center; 674 | align-items: center; 675 | color: white; 676 | font-size: 24px; 677 | text-align: center; 678 | z-index: 99; 679 | } 680 | 681 | 682 | .card .progress-container { 683 | width: 100%; 684 | height: 100%; 685 | background-color: #e0e0e0; 686 | position: relative; 687 | 688 | } 689 | 690 | .card .progress-bar { 691 | z-index: 98; 692 | height: 100%; 693 | background-color: #76c7c04f; 694 | width: 0%; 695 | transition: width 0.3s; 696 | position: absolute; 697 | } 698 | 699 | 700 | 701 | .card .sub-stripe-status{ 702 | font-size: 1rem; 703 | background: #00000073; 704 | padding: 3px 10px; 705 | border-radius: 11px; 706 | display: none; 707 | } 708 | 709 | 710 | .sub-stripe-manager { 711 | display: flex; 712 | flex-direction: column; /* I div figli saranno disposti verticalmente */ 713 | align-items: flex-start; /* Allineamento a sinistra */ 714 | } 715 | .sub-stripe-manager div { 716 | 717 | padding: 2px 9px; 718 | transition: filter 0.3s ease; 719 | } 720 | .sub-stripe-manager div:hover { 721 | filter: brightness(180%); /* Aumenta la luminosità del 10% */ 722 | } 723 | .sub-stripe-manager{ 724 | position: absolute; 725 | right: 28px; 726 | top: 0; 727 | right: 0; 728 | } 729 | 730 | .card .sub-stripe-dw{ 731 | background: #ff4141; 732 | border-bottom-left-radius: 6px; 733 | } 734 | 735 | .card .sub-stripe-ps{ 736 | background: #ff6720; 737 | display: none; 738 | filter: brightness(80%); 739 | 740 | } 741 | 742 | .card .sub-stripe-cn{ 743 | background: #ff4141; 744 | border-bottom-left-radius: 6px; 745 | display: none; 746 | filter: brightness(80%); 747 | } 748 | .card .sub-stripe-complete{ 749 | background: #00c027; 750 | 751 | display: none; 752 | 753 | } 754 | .card .sub-stripe-delete{ 755 | background: #ff4141; 756 | border-bottom-left-radius: 6px; 757 | display: none; 758 | 759 | } 760 | 761 | .sb-pi-toast-center { 762 | padding-top: 5px; 763 | } -------------------------------------------------------------------------------- /app/panels/custom_categories/custom_categories.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 8 | 9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /app/panels/custom_categories/custom_categories.jsb: -------------------------------------------------------------------------------- 1 | //console.log(LiteGraph.registered_node_types); 2 | var custom_categories = (function () { 3 | let currentElement = null; 4 | 5 | function createCustomHtml(type, name, elem = null) { 6 | if (type == "rename") { 7 | return `

Rename Category

8 |
9 | 10 | 11 |
12 | 13 | 14 | ` 15 | } else if (type == "color") { 16 | currentElement = elem; 17 | return `

Color ${name}

18 |
19 | 20 |
21 | 22 | 23 | 24 | 25 | ` 26 | } 27 | } 28 | // Function to open the modal 29 | 30 | //insert button in label with id panel_title_custom_categories 31 | let categorySearchToggleCustom = false; 32 | const panel_title_custom_categories = document.getElementById("panel_title_custom_categories"); 33 | 34 | const button_custom_categories = document.createElement("button"); 35 | button_custom_categories.classList = "expand_node"; 36 | button_custom_categories.title = "Expand/Collapse"; 37 | button_custom_categories.innerHTML = ` 38 | 39 | 41 | 43 | 44 | 45 | `; 46 | 47 | panel_title_custom_categories.appendChild(button_custom_categories); 48 | 49 | button_custom_categories.addEventListener("click", (event) => { 50 | const clickedElement = event.target; 51 | const pinButton = clickedElement.tagName; 52 | if ((pinButton == "rect") && clickedElement.classList.contains("expand_node")) { 53 | 54 | sdExpandAll(); 55 | } 56 | 57 | }) 58 | const clearIconCustomCategory = document.querySelector(".clearIconCustomCategory"); 59 | const itemSearchInput = document.getElementById("searchInputCn"); 60 | clearIconCustomCategory.addEventListener("click", async function () { 61 | try { 62 | 63 | itemSearchInput.value = ""; 64 | renderList("custom_categories_main"); 65 | 66 | } catch (error) { 67 | console.error("Error occurred during search:", error); 68 | } 69 | }); 70 | 71 | 72 | 73 | 74 | 75 | function afterRender() { 76 | 77 | customMenu = ` 78 | { 79 | "menuctx_category": "Context Menu", 80 | "menuctx_options": [ 81 | "Rename Category", 82 | "Delete Category", 83 | "Change Color" 84 | ], 85 | "menuctx_subOptions": [ 86 | [ 87 | ] 88 | ], 89 | "menuctx_opt_callback": [ 90 | "custom_categories.renameCategoryCallback", 91 | "custom_categories.deleteCategory", 92 | "custom_categories.colorCategoryCallback" 93 | 94 | ], 95 | "menuctx_sub_opt_callback": [ 96 | [ 97 | ], 98 | [ 99 | ] 100 | ] 101 | }` 102 | 103 | customMenuItem = ` 104 | { 105 | "menuctx_category": "Context Menu", 106 | "menuctx_options": [ 107 | "Delete from Category" 108 | ], 109 | "menuctx_subOptions": [ 110 | [ 111 | ] 112 | ], 113 | "menuctx_opt_callback": [ 114 | "custom_categories.removeNodeFromCategoryCallback" 115 | 116 | ], 117 | "menuctx_sub_opt_callback": [ 118 | [ 119 | ], 120 | [ 121 | ] 122 | ] 123 | }` 124 | 125 | 126 | customMenuJSON = JSON.parse(customMenu); 127 | customMenuItemJSON = JSON.parse(customMenuItem); 128 | 129 | setContextMenu(customMenuJSON, "#custom_categories_main .sidebarCategory", 0, "sidebarItem", "sidebarCategory"); 130 | 131 | setContextMenu(customMenuItemJSON, "#custom_categories_main .sidebarItem"); 132 | 133 | 134 | 135 | // ORDER CUSTOM CATEGORIES 136 | 137 | 138 | const custom_categories_main = document.getElementById("custom_categories_main"); 139 | custom_categories_main.querySelectorAll(".sidebarCategory").forEach(function (item) { 140 | item.addEventListener("dragstart", function (event) { 141 | dragItem = event.target; 142 | 143 | 144 | }); 145 | 146 | }); 147 | 148 | custom_categories_main.addEventListener("dragover", function (event) { 149 | event.preventDefault(); 150 | }); 151 | 152 | custom_categories_main.addEventListener("drop", function (event) { 153 | event.preventDefault(); 154 | 155 | if (dragItem) { 156 | 157 | 158 | if (dragItem.parentElement === event.target.parentElement) { 159 | custom_categories_main.insertBefore(dragItem, event.target); 160 | reorderCategories(getCustomCategoryOrder()); 161 | } 162 | dragItem = null; 163 | } 164 | }); 165 | 166 | 167 | 168 | 169 | 170 | // ORDER CUSTOM NODES 171 | 172 | 173 | custom_categories_main.querySelectorAll(".sidebarItem").forEach(function (item) { 174 | item.addEventListener("dragstart", function (event) { 175 | dragItem = event.target; 176 | 177 | 178 | }); 179 | 180 | }); 181 | 182 | 183 | } 184 | 185 | function getCustomCategoryOrder() { 186 | let cat = []; 187 | const custom_categories_main = document.getElementById("custom_categories_main"); 188 | custom_categories_main.querySelectorAll(".sidebarCategory").forEach(function (item) { 189 | cat.push(item.dataset.namecategory) 190 | 191 | }); 192 | return cat 193 | } 194 | 195 | 196 | function getCustomNodeOrder(idcat) { 197 | let node = []; 198 | const objMain = document.getElementById(idcat); 199 | objMain.querySelectorAll(".sidebarItem").forEach(function (item) { 200 | node.push(item.dataset.type) 201 | 202 | }); 203 | return node 204 | } 205 | 206 | 207 | async function renderList(elementID) { 208 | const data = LiteGraph.registered_node_types; 209 | 210 | 211 | const myPromise = new Promise(async (resolve, reject) => { 212 | const categories = await readCategories(); 213 | //const pinnedItems = loadPinnedItems(); 214 | 215 | //console.log(categories); 216 | const sidebarCustomNodes = document.getElementById(elementID); 217 | //clear elementid 218 | sidebarCustomNodes.innerHTML = ""; 219 | 220 | 221 | for (const category in categories) { 222 | //data[objKey].title.toLowerCase(); 223 | let categoryName = categories[category] 224 | const categoryItem = document.createElement("li"); 225 | categoryItem.classList.add("sidebarCategory"); 226 | categoryItem.dataset.namecategory = categoryName; 227 | categoryItem.textContent = "⌬ " + categoryName; 228 | categoryItem.draggable = true; 229 | 230 | const currentColor = await getValueFromConfig("sb_ColorCustomCategories", categoryName); 231 | if (currentColor) { 232 | const value_perc = await getConfiguration("sb_opacity") || "1.0"; 233 | 234 | const rgbColor = hexToRgb(currentColor); 235 | categoryItem.style.setProperty('background-color', `rgba(${rgbColor}, ${value_perc})`, 'important'); 236 | 237 | //categoryItem.style.backgroundColor = `rgba(${rgbColor}, ${value_perc})`; 238 | } 239 | 240 | 241 | 242 | 243 | const displayNamesList = document.createElement("ul"); 244 | displayNamesList.id = categoryName + "_ul"; 245 | displayNamesList.style.display = getNodeStatus('sb_categoryNodeStatus',categoryName); 246 | categoryItem.appendChild(displayNamesList); 247 | 248 | let displayName = category; 249 | 250 | 251 | const listNodeForCategory = await getNodesForCategory(categoryName); 252 | 253 | 254 | 255 | listNodeForCategory.forEach(displayName => { 256 | try { 257 | const displayNameItem = document.createElement("li"); 258 | displayNameItem.classList.add("sidebarItem"); 259 | displayNameItem.textContent = data[displayName].title; 260 | displayNameItem.title = data[displayName].title; 261 | displayNameItem.draggable = true; 262 | displayNameItem.dataset.type = data[displayName].type; 263 | 264 | 265 | 266 | 267 | displayNameItem.id = displayName//.replace(" ", "_"); 268 | 269 | /* Create Pin Button 270 | const pinButton = document.createElement("button"); 271 | pinButton.classList.add("pinButton"); 272 | 273 | let add_class = ""; 274 | if (pinnedItems.includes(displayNameItem.dataset.type)) { 275 | add_class = "pinned"; 276 | } 277 | 278 | pinButton.innerHTML = ` 279 | 280 | 281 | `; 282 | 283 | 284 | displayNameItem.appendChild(pinButton); 285 | /* End Pin Button */ 286 | 287 | 288 | displayNamesList.appendChild(displayNameItem); 289 | 290 | 291 | displayNamesList.addEventListener("drop", function (event) { 292 | event.preventDefault(); 293 | 294 | if (dragItem) { 295 | 296 | if (dragItem.parentElement == event.target.parentElement) { 297 | 298 | displayNamesList.insertBefore(dragItem, event.target); 299 | 300 | reorderNodeInCategory(categoryItem.dataset.namecategory, getCustomNodeOrder(dragItem.parentElement.id)) 301 | 302 | } 303 | dragItem = null; 304 | 305 | 306 | } 307 | }); 308 | 309 | 310 | 311 | 312 | 313 | } 314 | catch (err) { 315 | console.log(err); 316 | } 317 | }) 318 | 319 | 320 | 321 | 322 | try { 323 | sidebarCustomNodes.appendChild(categoryItem); 324 | } catch (err) { 325 | console.log(err); 326 | } 327 | 328 | 329 | } 330 | 331 | 332 | 333 | 334 | // Preview 335 | const sidebarItems_cat = document.querySelectorAll('#' + elementID + ' .sidebarItem'); 336 | const previewDiv = document.getElementById('previewDiv'); 337 | let sidebad_view_width = document.getElementById("sidebar_views").offsetWidth; 338 | 339 | sidebarItems_cat.forEach(item => { 340 | 341 | getPreview(item,previewDiv,sidebad_view_width,(item) => { 342 | return createNodePreview(item.id); 343 | }); 344 | 345 | }); 346 | 347 | 348 | 349 | const categoriesList = document.getElementsByClassName("content_sidebar")[1]; 350 | categoriesList.addEventListener('scroll', function () { 351 | previewDiv.style.display = 'none'; 352 | }); 353 | 354 | window.addEventListener('click', function (event) { 355 | 356 | if (!event.target.classList.contains('sidebarItem')) { 357 | previewDiv.style.display = 'none'; 358 | 359 | } 360 | }); 361 | 362 | 363 | 364 | const categoryItems = document.querySelectorAll("#custom_categories_main .sidebarCategory"); 365 | categoryItems.forEach(function (categoryItem) { 366 | categoryItem.addEventListener("click", function (event) { 367 | 368 | if (event.target === event.currentTarget) { 369 | const displayNamesList = event.target.querySelector("ul"); 370 | 371 | displayNamesList.style.display = displayNamesList.style.display === "none" ? "block" : "none"; 372 | 373 | setNodeStatus('sb_categoryNodeStatus',displayNamesList.parentElement.dataset.namecategory, displayNamesList.style.display) 374 | } 375 | }); 376 | 377 | 378 | 379 | }); 380 | resolve(afterRender); 381 | 382 | 383 | 384 | 385 | 386 | }); 387 | myPromise.then((callback) => { 388 | 389 | callback(); 390 | }).catch((error) => { 391 | console.error("An error occurred:", error); 392 | }); 393 | } 394 | function searchT(e) { 395 | 396 | if (e.value != "") { 397 | //search in all .sidebarItem 398 | handleSearch(categorySearchToggleCustom, "#custom_categories_main", "searchInputCn") 399 | 400 | 401 | } else { 402 | 403 | renderList("custom_categories_main"); 404 | } 405 | 406 | } 407 | 408 | 409 | 410 | async function getNodeMap() { 411 | let categoryNodeMap = JSON.parse(await getConfiguration('sb_categoryNodeMap')) || {}; 412 | return categoryNodeMap; 413 | } 414 | 415 | function getNodesStatus() { 416 | let categoryNodeStatus = JSON.parse(localStorage.getItem('sb_categoryNodeStatus')) || {}; 417 | return categoryNodeStatus; 418 | } 419 | 420 | 421 | async function addCategory() { 422 | 423 | const newCategoryName = document.getElementById('sb-categoryName').value; 424 | // Check if the category name contains special characters 425 | if (containsSpecialCharacters(newCategoryName)) { 426 | // Alert the user and return if special characters are found 427 | alert("Category name cannot contain special characters."); 428 | return; 429 | } 430 | 431 | let categoryNodeMap = await getNodeMap(); 432 | 433 | if (!categoryNodeMap[newCategoryName]) { 434 | 435 | categoryNodeMap[newCategoryName] = []; 436 | updateConfiguration('sb_categoryNodeMap', JSON.stringify(categoryNodeMap)); 437 | createContextualMenu(); 438 | reloadCtxMenu() 439 | renderList("custom_categories_main"); 440 | closeModal() 441 | } else { 442 | alert("Category already exists!"); 443 | return; 444 | } 445 | } 446 | 447 | 448 | // Function to rename a category 449 | 450 | async function renameCategory(oldCategoryName, newCategoryName) { 451 | 452 | 453 | if (containsSpecialCharacters(newCategoryName)) { 454 | // Alert the user and return if special characters are found 455 | alert("Category name cannot contain special characters."); 456 | return; 457 | } 458 | if (oldCategoryName === newCategoryName) { 459 | closeModal() 460 | return; 461 | } 462 | 463 | let categoryNodeMap = await getNodeMap(); 464 | 465 | if (categoryNodeMap[newCategoryName]) { 466 | alert("Category already exists!"); 467 | return; 468 | } 469 | 470 | 471 | let categoryNodeStatus = getNodesStatus(); 472 | if (categoryNodeMap[oldCategoryName]) { 473 | // Categoty Current Order 474 | let keys = Object.keys(categoryNodeMap); 475 | 476 | categoryNodeMap[newCategoryName] = categoryNodeMap[oldCategoryName]; 477 | delete categoryNodeMap[oldCategoryName]; 478 | 479 | // Rebuild Order 480 | let updatedMap = {}; 481 | keys.forEach(key => { 482 | if (key === oldCategoryName) { 483 | updatedMap[newCategoryName] = categoryNodeMap[newCategoryName]; 484 | } else { 485 | updatedMap[key] = categoryNodeMap[key]; 486 | } 487 | }); 488 | 489 | 490 | 491 | updateConfiguration('sb_categoryNodeMap', JSON.stringify(updatedMap)); 492 | //rename status 493 | if (categoryNodeStatus[oldCategoryName]) { 494 | 495 | categoryNodeStatus[newCategoryName] = categoryNodeStatus[oldCategoryName]; 496 | delete categoryNodeStatus[oldCategoryName]; 497 | localStorage.setItem('sb_categoryNodeStatus', JSON.stringify(categoryNodeStatus)); 498 | } 499 | //rename color 500 | renameKeyConfig("sb_ColorCustomCategories", oldCategoryName, newCategoryName) 501 | 502 | 503 | createContextualMenu(); 504 | reloadCtxMenu() 505 | renderList("custom_categories_main"); 506 | closeModal() 507 | } else { 508 | alert("Category not found!"); 509 | } 510 | } 511 | async function readCategories() { 512 | let categoryNodeMap = await getNodeMap(); 513 | if (categoryNodeMap) { 514 | return Object.keys(categoryNodeMap); 515 | } 516 | else { 517 | return []; 518 | } 519 | } 520 | // Function to delete a category 521 | async function deleteCategory(e) { 522 | 523 | const name = e.target.dataset.namecategory; 524 | confirmation = confirm("Are you sure you want to delete the category " + name + "?"); 525 | if (!confirmation) { return; } 526 | 527 | let categoryNodeMap = await getNodeMap(); 528 | // If the category name is found, remove it from the array 529 | if (categoryNodeMap[name]) { 530 | removeNodeMapCategory(name); 531 | createContextualMenu(); 532 | removeKeyFromConfig("sb_ColorCustomCategories", name) 533 | reloadCtxMenu() 534 | setTimeout(() => { 535 | 536 | renderList("custom_categories_main"); 537 | },300) 538 | 539 | } else { 540 | alert("Category not found!"); 541 | } 542 | } 543 | 544 | 545 | 546 | 547 | 548 | ///////////////////////////////////////////////// 549 | 550 | async function getConfig(configName) { 551 | let categoryNodeMap = JSON.parse(await getConfiguration(configName)) || {}; 552 | return categoryNodeMap; 553 | } 554 | 555 | 556 | 557 | async function getValueFromConfig(configName, key) { 558 | let config = await getConfig(configName); 559 | return config[key] || null; 560 | } 561 | 562 | async function assignValueToConfig(configName, key, value) { 563 | let config = await getConfig(configName); 564 | config[key] = value; 565 | updateConfiguration(configName, JSON.stringify(config)); 566 | } 567 | 568 | async function removeKeyFromConfig(configName, key) { 569 | let config = await getConfig(configName); 570 | delete config[key]; 571 | updateConfiguration(configName, JSON.stringify(config)); 572 | 573 | 574 | } 575 | async function renameKeyConfig(configName, oldKey, newKey) { 576 | let config = await getConfig(configName); 577 | let value = config[oldKey]; 578 | delete config[oldKey]; 579 | config[newKey] = value; 580 | updateConfiguration(configName, JSON.stringify(config)); 581 | } 582 | 583 | async function removeKeyConfig(configName) { 584 | removeConfiguration(configName) 585 | renderList("custom_categories_main"); 586 | } 587 | 588 | 589 | 590 | //////////////////////////////////////////////// 591 | 592 | async function getCategoriesForNode(nodeName) { 593 | let categoryNodeMap = await getNodeMap(); 594 | 595 | let categories = []; 596 | for (let category in categoryNodeMap) { 597 | if (categoryNodeMap.hasOwnProperty(category)) { 598 | if (categoryNodeMap[category].includes(nodeName)) { 599 | categories.push(category); 600 | } 601 | } 602 | } 603 | return categories; 604 | } 605 | 606 | async function getNodesForCategory(categoryName) { 607 | let categoryNodeMap = await getNodeMap(); 608 | 609 | return categoryNodeMap[categoryName] || []; 610 | } 611 | 612 | 613 | async function assignNodeToCategory(nodeName, categoryNames) { 614 | let categoryNodeMap = await getNodeMap(); 615 | let assignedCategories = await getCategoriesForNode(nodeName); 616 | categoryNames = categoryNames.filter(category => !assignedCategories.includes(category)); 617 | 618 | categoryNames.forEach(categoryName => { 619 | if (!categoryNodeMap[categoryName]) { 620 | categoryNodeMap[categoryName] = []; 621 | } 622 | categoryNodeMap[categoryName].push(nodeName); 623 | }); 624 | updateConfiguration('sb_categoryNodeMap', JSON.stringify(categoryNodeMap)); 625 | renderList("custom_categories_main"); 626 | } 627 | 628 | 629 | async function removeNodeFromCategory(nodeName, categoryName) { 630 | let categoryNodeMap = await getNodeMap(); 631 | 632 | if (categoryNodeMap[categoryName]) { 633 | categoryNodeMap[categoryName] = categoryNodeMap[categoryName].filter(node => node !== nodeName); 634 | updateConfiguration('sb_categoryNodeMap', JSON.stringify(categoryNodeMap)); 635 | renderList("custom_categories_main"); 636 | 637 | } 638 | } 639 | 640 | 641 | async function removeNodeMapCategory(categoryName) { 642 | let categoryNodeMap = await getNodeMap(); 643 | let categoryNodeStatus = getNodesStatus(); 644 | delete categoryNodeMap[categoryName]; 645 | updateConfiguration('sb_categoryNodeMap', JSON.stringify(categoryNodeMap)); 646 | 647 | if (categoryNodeStatus[categoryName]) { 648 | delete categoryNodeStatus[categoryName]; 649 | localStorage.setItem('sb_categoryNodeStatus', JSON.stringify(categoryNodeStatus)); 650 | 651 | } 652 | } 653 | 654 | 655 | function editNodeCategory(oldCategoryName, newCategoryName, nodeName) { 656 | 657 | removeNodeFromCategory(nodeName, oldCategoryName); 658 | 659 | assignNodeToCategory(nodeName, [newCategoryName]); 660 | } 661 | 662 | 663 | 664 | async function reorderCategories(orderedKeys) { 665 | let categoryNodeMap = await getNodeMap(); 666 | let reorderedCategoryNodeMap = {}; 667 | 668 | 669 | orderedKeys.forEach(categoryKey => { 670 | if (categoryNodeMap.hasOwnProperty(categoryKey)) { 671 | reorderedCategoryNodeMap[categoryKey] = categoryNodeMap[categoryKey]; 672 | } 673 | }); 674 | 675 | updateConfiguration('sb_categoryNodeMap', JSON.stringify(reorderedCategoryNodeMap)); 676 | } 677 | 678 | 679 | async function reorderNodeInCategory(categoryName, orderedKeys) { 680 | let categoryNodeMap = await getNodeMap(); 681 | 682 | if (categoryNodeMap[categoryName]) { 683 | categoryNodeMap[categoryName] = orderedKeys; 684 | updateConfiguration('sb_categoryNodeMap', JSON.stringify(categoryNodeMap)); 685 | } 686 | } 687 | 688 | // Callbacks 689 | function removeNodeFromCategoryCallback(e, trge) { 690 | 691 | removeNodeFromCategory(e.target.dataset.type, e.target.parentElement.parentElement.dataset.namecategory) 692 | } 693 | 694 | function assignNodeToCategoryCallback(e, trge) { 695 | assignNodeToCategory(e.target.id, [trge]); 696 | } 697 | 698 | function renameCategoryCallback(e, trge) { 699 | const name = e.target.dataset.namecategory; 700 | openModal(createCustomHtml("rename", name)) 701 | 702 | } 703 | 704 | function colorCategoryCallback(e) { 705 | 706 | const name = e.target.dataset.namecategory; 707 | openModal(createCustomHtml("color", name, e.target)) 708 | //colorCategory(e.target.id, e.target.parentElement.parentElement.dataset.namecategory) 709 | } 710 | 711 | 712 | 713 | 714 | async function colorCategory(name, value) { 715 | 716 | 717 | const value_perc = await getConfiguration("sb_opacity") || "1.0"; 718 | const rgbColor = hexToRgb(value); 719 | 720 | 721 | currentElement.style.setProperty('background-color', `rgba(${rgbColor}, ${value_perc})`, 'important'); 722 | 723 | assignValueToConfig("sb_ColorCustomCategories", name, value) 724 | } 725 | // Add an event listener to the button to open the modal 726 | function resetColorCategory(name) { 727 | 728 | removeKeyFromConfig("sb_ColorCustomCategories", name) 729 | renderList("custom_categories_main"); 730 | 731 | } 732 | 733 | 734 | async function createContextualMenu() { 735 | 736 | //const menuOptions = document.getElementById('menu-options'); 737 | new_menu_options = []; 738 | new_submenu_options = []; 739 | new_menu_options_callback = []; 740 | new_submenu_options_callback = []; 741 | new_menu_options.push("Add to category"); 742 | /*new_menu_options.push("Pin It"); 743 | new_menu_options_callback.push("dummy"); 744 | new_menu_options_callback.push("test_altert");*/ 745 | //for each category 746 | category_menu = await readCategories(); 747 | callback_menu = []; 748 | for (let i = 0; i < category_menu.length; i++) { 749 | callback_menu.push("custom_categories.assignNodeToCategoryCallback"); 750 | 751 | } 752 | new_submenu_options.push(category_menu); 753 | new_submenu_options_callback.push(callback_menu); 754 | 755 | 756 | console.log("custom category panel loaded") 757 | 758 | } 759 | 760 | 761 | function test_altert(e, trge) { 762 | console.log(trge); 763 | console.log(e.target.id); 764 | console.log(e.target.parentElement.parentElement.dataset.namecategory); 765 | 766 | assignNodeToCategory(e.target.id, [trge]); 767 | } 768 | 769 | function dummy(e, trge) { 770 | 771 | } 772 | 773 | 774 | 775 | const searchCategoryIconCustomCategory = document.querySelector(".searchCategoryIconCustomCategory"); 776 | 777 | 778 | searchCategoryIconCustomCategory.addEventListener("click", async function () { 779 | 780 | try { 781 | 782 | categorySearchToggleCustom = !categorySearchToggleCustom; 783 | searchCategoryIconCustomCategory.style.opacity = categorySearchToggleCustom ? "1" : "0.5"; 784 | 785 | 786 | } catch (error) { 787 | 788 | console.error("Error occurred during search:", error); 789 | } 790 | }); 791 | return { 792 | openModal, 793 | addSidebarStyles, 794 | createContextualMenu, 795 | renderList, 796 | renameCategory, 797 | colorCategory, 798 | removeKeyConfig, 799 | searchT, 800 | deleteCategory, 801 | addCategory, 802 | editNodeCategory, 803 | removeNodeMapCategory, 804 | reorderNodeInCategory, 805 | removeNodeFromCategoryCallback, 806 | assignNodeToCategoryCallback, 807 | renameCategoryCallback, 808 | renameCategoryCallback, 809 | colorCategoryCallback, 810 | resetColorCategory 811 | }; 812 | 813 | 814 | })(); 815 | custom_categories.addSidebarStyles("panels/custom_categories/style.css"); 816 | custom_categories.createContextualMenu() 817 | custom_categories.renderList("custom_categories_main"); -------------------------------------------------------------------------------- /app/panels/custom_categories/style.css: -------------------------------------------------------------------------------- 1 | #custom_categories_main,#custom_categories_header { 2 | 3 | justify-content: center; 4 | align-items: center; 5 | height: 100%; 6 | margin: 0; 7 | } 8 | #custom_categories_main{ 9 | 10 | padding-top: 44px; 11 | padding-bottom: 150px; 12 | } 13 | #custom_categories_header { 14 | margin: 10px 20px; 15 | font-size: 16px; 16 | 17 | } 18 | #custom_categories_header #h_search { 19 | position: absolute; 20 | width: calc(100% - 76px); 21 | margin-top: 0px; 22 | left: 10px; 23 | z-index: 400; 24 | 25 | } 26 | 27 | #custom_categories_header button { 28 | /* padding: 10px 20px; */ 29 | height: 35px; 30 | width: 35px; 31 | font-size: 16px; 32 | background: var(--comfy-input-bg); 33 | border: 2px solid var(--border-color); 34 | color: var(--input-text); 35 | border-radius: 5px; 36 | float: right; 37 | position: relative; 38 | margin: 3px 2px 2px 8px; 39 | cursor: pointer; 40 | z-index: 999; 41 | } 42 | 43 | #custom_categories_header button:hover {border: 2px solid var(--descrip-text); 44 | 45 | } 46 | .sb-category-dialog { 47 | outline: 0; 48 | border: 0; 49 | border-radius: 6px; 50 | background: var(--bg-color); 51 | color: #fff; 52 | box-shadow: inset 1px 1px 0px rgba(255, 255, 255, 0.05), inset -1px -1px 0px rgba(0, 0, 0, 0.5), 2px 2px 20px rgb(0, 0, 0); 53 | max-width: 800px; 54 | min-width: 30rem; 55 | box-sizing: border-box; 56 | font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 57 | font-size: 1rem; 58 | padding: 20px; 59 | position: fixed; 60 | top: 50%; 61 | left: 50%; 62 | z-index: 1001; 63 | } 64 | 65 | #sb-category-form { 66 | margin-top: 10px; 67 | text-align: center; 68 | } 69 | 70 | .sb-input { 71 | width: 100%; 72 | padding: 8px; 73 | margin-bottom: 10px; 74 | box-sizing: border-box; 75 | border: 2px solid var(--border-color); 76 | border-radius: 4px; 77 | margin-top: 16px; 78 | background: var(--bg-color); 79 | color: var(--input-text); 80 | 81 | } 82 | .sb-input:focus { 83 | outline: none; 84 | border: 2px solid var(--descrip-text); 85 | } 86 | 87 | .sb-button { 88 | background-color: var(--comfy-menu-bg); 89 | color: var(--input-text); 90 | padding: 10px 20px; 91 | border: none; 92 | border-radius: 4px; 93 | cursor: pointer; 94 | margin-top: 10px; 95 | text-transform: uppercase; 96 | font-weight: bold; 97 | } 98 | 99 | .sb-button:hover { 100 | filter: brightness(120%); 101 | } 102 | 103 | #closeModalButton { 104 | background-color: var(--comfy-menu-bg); 105 | position: absolute; 106 | top: 0; 107 | right: 0; 108 | margin: 0; 109 | } 110 | 111 | #closeModalButton:hover { 112 | filter: brightness(120%); 113 | 114 | } 115 | #searchInputCn { 116 | box-sizing: border-box; 117 | width: 100%; 118 | border-radius: 5px; 119 | padding: 10px; 120 | border: none; 121 | user-select: none; 122 | background: var(--comfy-input-bg); 123 | color: var(--input-text); 124 | line-height: 1.4; 125 | border: 1px solid var(--border-color); 126 | } -------------------------------------------------------------------------------- /app/panels/custom_templates/custom_templates.html: -------------------------------------------------------------------------------- 1 |
2 | 9 | 10 | 11 |
12 |
13 |
-------------------------------------------------------------------------------- /app/panels/custom_templates/style.css: -------------------------------------------------------------------------------- 1 | #custom_templates_main,#custom_templates_header { 2 | 3 | justify-content: center; 4 | align-items: center; 5 | height: 44px; 6 | margin: 0; 7 | } 8 | #custom_templates_main{ 9 | height: auto; 10 | padding-top: 0px; 11 | padding-bottom: 150px; 12 | } 13 | #custom_templates_header { 14 | margin: 10px 0px; 15 | font-size: 16px; 16 | 17 | } 18 | #custom_templates_header #h_search { 19 | position: absolute; 20 | width: calc(100% - 92px); 21 | margin-top: 0px; 22 | left: 10px; 23 | z-index: 400; 24 | 25 | } 26 | 27 | #custom_templates_header button { 28 | /* padding: 10px 20px; */ 29 | height: 35px; 30 | width: 35px; 31 | font-size: 16px; 32 | background: var(--comfy-input-bg); 33 | border: 2px solid var(--border-color); 34 | color: var(--input-text); 35 | border-radius: 5px; 36 | float: right; 37 | position: relative; 38 | margin: 3px 2px 2px 2px; 39 | cursor: pointer; 40 | z-index: 999; 41 | } 42 | 43 | #custom_templates_header button:hover { 44 | border: 2px solid var(--descrip-text); 45 | 46 | } 47 | .sb-template-dialog { 48 | outline: 0; 49 | border: 0; 50 | border-radius: 6px; 51 | background: var(--bg-color); 52 | color: #fff; 53 | box-shadow: inset 1px 1px 0px rgba(255, 255, 255, 0.05), inset -1px -1px 0px rgba(0, 0, 0, 0.5), 2px 2px 20px rgb(0, 0, 0); 54 | max-width: 800px; 55 | min-width: 30rem; 56 | box-sizing: border-box; 57 | font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 58 | font-size: 1rem; 59 | padding: 20px; 60 | position: fixed; 61 | top: 50%; 62 | left: 50%; 63 | z-index: 1001; 64 | } 65 | 66 | #sb-template-form { 67 | margin-top: 10px; 68 | text-align: center; 69 | } 70 | 71 | .sb-input { 72 | width: 100%; 73 | padding: 8px; 74 | margin-bottom: 10px; 75 | box-sizing: border-box; 76 | border: 2px solid var(--border-color); 77 | border-radius: 4px; 78 | margin-top: 16px; 79 | background: var(--bg-color); 80 | color: var(--input-text); 81 | 82 | } 83 | .sb-input:focus { 84 | outline: none; 85 | border: 2px solid var(--descrip-text); 86 | } 87 | 88 | .sb-button { 89 | background-color: var(--comfy-menu-bg); 90 | color: var(--input-text); 91 | padding: 10px 20px; 92 | border: none; 93 | border-radius: 4px; 94 | cursor: pointer; 95 | margin-top: 10px; 96 | text-transform: uppercase; 97 | font-weight: bold; 98 | } 99 | 100 | .sb-button:hover { 101 | filter: brightness(120%); 102 | } 103 | 104 | #closeModalButton { 105 | background-color: var(--comfy-menu-bg); 106 | position: absolute; 107 | top: 0; 108 | right: 0; 109 | margin: 0; 110 | } 111 | 112 | #closeModalButton:hover { 113 | filter: brightness(120%); 114 | 115 | } 116 | #searchTemplateInput { 117 | box-sizing: border-box; 118 | width: 100%; 119 | border-radius: 5px; 120 | padding: 10px; 121 | border: none; 122 | user-select: none; 123 | background: var(--comfy-input-bg); 124 | color: var(--input-text); 125 | line-height: 1.4; 126 | border: 1px solid var(--border-color); 127 | } 128 | 129 | 130 | #previewDiv .sb_table { 131 | 132 | text-align: center; 133 | } 134 | 135 | .sidebar #previewDiv .sidebarItem, #custom_templates_main .sidebarItem { 136 | 137 | max-width: 100% !important; 138 | } 139 | 140 | #refreshlButton{ 141 | margin-left: 48px !important; 142 | } 143 | 144 | -------------------------------------------------------------------------------- /app/panels/custom_workflows/custom_workflows.html: -------------------------------------------------------------------------------- 1 |
2 | 9 | 10 | 11 |
12 |
13 |
14 |
-------------------------------------------------------------------------------- /app/panels/custom_workflows/style.css: -------------------------------------------------------------------------------- 1 | #custom_workflows_main,#custom_workflows_header { 2 | 3 | justify-content: center; 4 | align-items: center; 5 | height: 100%; 6 | margin: 0; 7 | } 8 | #custom_workflows_main{ 9 | 10 | padding-top: 44px; 11 | padding-bottom: 150px; 12 | } 13 | #custom_workflows_header { 14 | margin: 10px 0px; 15 | font-size: 16px; 16 | 17 | } 18 | #custom_workflows_header #h_search { 19 | position: absolute; 20 | width: calc(100% - 92px); 21 | margin-top: 0px; 22 | left: 10px; 23 | z-index: 400; 24 | 25 | } 26 | 27 | #custom_workflows_header button { 28 | /* padding: 10px 20px; */ 29 | height: 35px; 30 | width: 35px; 31 | font-size: 16px; 32 | background: var(--comfy-input-bg); 33 | border: 2px solid var(--border-color); 34 | color: var(--input-text); 35 | border-radius: 5px; 36 | float: right; 37 | position: relative; 38 | margin: 3px 2px 2px 2px; 39 | cursor: pointer; 40 | z-index: 999; 41 | } 42 | 43 | #custom_workflows_header button:hover { 44 | border: 2px solid var(--descrip-text); 45 | 46 | } 47 | 48 | 49 | .sidebarFolder { 50 | list-style-type: none; 51 | font-family: 'Open Sans', sans-serif; 52 | text-transform: capitalize; 53 | margin: 2px; 54 | background-color: var(--comfy-input-bg); 55 | opacity: 1; 56 | border-radius: 8px; 57 | padding-top: 11px; 58 | font-size: 15px; 59 | padding-bottom: 8px; 60 | } 61 | 62 | 63 | 64 | .sb-workflow-dialog { 65 | outline: 0; 66 | border: 0; 67 | border-radius: 6px; 68 | background: var(--bg-color); 69 | color: #fff; 70 | box-shadow: inset 1px 1px 0px rgba(255, 255, 255, 0.05), inset -1px -1px 0px rgba(0, 0, 0, 0.5), 2px 2px 20px rgb(0, 0, 0); 71 | max-width: 800px; 72 | min-width: 30rem; 73 | box-sizing: border-box; 74 | font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 75 | font-size: 1rem; 76 | padding: 20px; 77 | position: fixed; 78 | top: 50%; 79 | left: 50%; 80 | z-index: 1001; 81 | } 82 | 83 | #sb-workflow-form { 84 | margin-top: 10px; 85 | text-align: center; 86 | } 87 | 88 | .sb-input { 89 | width: 100%; 90 | padding: 8px; 91 | margin-bottom: 10px; 92 | box-sizing: border-box; 93 | border: 2px solid var(--border-color); 94 | border-radius: 4px; 95 | margin-top: 16px; 96 | background: var(--bg-color); 97 | color: var(--input-text); 98 | 99 | } 100 | .sb-input:focus { 101 | outline: none; 102 | border: 2px solid var(--descrip-text); 103 | } 104 | 105 | .sb-button { 106 | background-color: var(--comfy-menu-bg); 107 | color: var(--input-text); 108 | padding: 10px 20px; 109 | border: none; 110 | border-radius: 4px; 111 | cursor: pointer; 112 | margin-top: 10px; 113 | text-transform: uppercase; 114 | font-weight: bold; 115 | } 116 | 117 | .sb-button:hover { 118 | filter: brightness(120%); 119 | } 120 | 121 | #closeModalButton { 122 | background-color: var(--comfy-menu-bg); 123 | position: absolute; 124 | top: 0; 125 | right: 0; 126 | margin: 0; 127 | } 128 | 129 | #closeModalButton:hover { 130 | filter: brightness(120%); 131 | 132 | } 133 | #searchWorkflowInput { 134 | box-sizing: border-box; 135 | width: 100%; 136 | border-radius: 5px; 137 | padding: 10px; 138 | border: none; 139 | user-select: none; 140 | background: var(--comfy-input-bg); 141 | color: var(--input-text); 142 | line-height: 1.4; 143 | border: 1px solid var(--border-color); 144 | } 145 | 146 | 147 | #previewDiv .sb_table { 148 | 149 | text-align: center; 150 | } 151 | 152 | .sidebar #previewDiv .sidebarItem, #custom_workflows_main .sidebarItem { 153 | 154 | max-width: 100% !important; 155 | } 156 | #refreshlButton{ 157 | margin-left: 48px !important; 158 | } 159 | 160 | 161 | .TIPSidebarItem { 162 | color: var(--border-color); 163 | 164 | font-family: 'Open Sans'; 165 | font-size: 2rem; 166 | font-weight: bold; 167 | text-align: center; 168 | 169 | } -------------------------------------------------------------------------------- /app/panels/terminal/style.css: -------------------------------------------------------------------------------- 1 | .terminal { 2 | width: 100%; 3 | height: 400px; 4 | background-color: #000; 5 | padding: 10px; 6 | overflow-y: auto; 7 | white-space: pre-wrap; /* Maintains spaces and line breaks */ 8 | } 9 | .input-area { 10 | display: flex; 11 | } 12 | .input-field { 13 | flex: 1; 14 | background-color: #333; 15 | color: #fff; 16 | border: none; 17 | padding: 5px; 18 | } 19 | .submit-btn { 20 | background-color: #007bff; 21 | color: #fff; 22 | padding: 5px 10px; 23 | border: none; 24 | cursor: pointer; 25 | } -------------------------------------------------------------------------------- /app/panels/terminal/terminal.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 | 7 |
8 | 9 |
10 | 11 | 12 |
13 |
-------------------------------------------------------------------------------- /app/panels/terminal/terminal.jsb: -------------------------------------------------------------------------------- 1 | var custom_example= (function () { 2 | 3 | let history = []; 4 | let historyIndex = -1; 5 | 6 | function escapeHtml(html) { 7 | const text = document.createTextNode(html); 8 | const p = document.createElement('p'); 9 | p.appendChild(text); 10 | return p.innerHTML; 11 | } 12 | 13 | function sendCommand() { 14 | const inputField = document.getElementById('terminal-input'); 15 | const command = inputField.value; 16 | 17 | if (command.trim() === '') return; 18 | 19 | // Add command to terminal 20 | document.getElementById('terminal-output').innerHTML += `
> ${escapeHtml(command)}
`; 21 | 22 | // Send command to backend 23 | fetch('/sidebar/execute', { 24 | method: 'POST', 25 | headers: { 26 | 'Content-Type': 'application/json', 27 | }, 28 | body: JSON.stringify({ command }), 29 | }) 30 | .then(response => response.json()) 31 | .then(data => { 32 | // Display output 33 | const output = data.output;//.replace(/\n/g, '
'); // Convert line breaks to
34 | document.getElementById('terminal-output').innerHTML += `
${escapeHtml(output)}
`; 35 | // Scroll to the bottom 36 | const terminal = document.getElementById('terminal-output'); 37 | terminal.scrollTop = terminal.scrollHeight; 38 | }) 39 | .catch(error => { 40 | document.getElementById('terminal-output').innerHTML += `
Error: ${escapeHtml(error.message)}
`; 41 | // Scroll to the bottom 42 | const terminal = document.getElementById('terminal-output'); 43 | terminal.scrollTop = terminal.scrollHeight; 44 | }); 45 | 46 | // Add command to history and clear input field 47 | if (history.length === 0 || history[history.length - 1] !== command) { 48 | history.push(command); 49 | } 50 | historyIndex = history.length; 51 | inputField.value = ''; 52 | } 53 | 54 | function handleKeyDown(event) { 55 | const inputField = document.getElementById('terminal-input'); 56 | if (event.key === 'Enter') { 57 | event.preventDefault(); // Prevent the default action (form submission or newline) 58 | sendCommand(); 59 | } else if (event.key === 'ArrowUp') { 60 | event.preventDefault(); 61 | if (historyIndex > 0) { 62 | historyIndex--; 63 | inputField.value = history[historyIndex]; 64 | } 65 | } else if (event.key === 'ArrowDown') { 66 | event.preventDefault(); 67 | if (historyIndex < history.length - 1) { 68 | historyIndex++; 69 | inputField.value = history[historyIndex]; 70 | } else { 71 | historyIndex = history.length; 72 | inputField.value = ''; 73 | } 74 | } 75 | } 76 | 77 | // Add event listener for Enter key and Arrow keys 78 | document.getElementById('terminal-input').addEventListener('keydown', handleKeyDown); 79 | 80 | return { 81 | sendCommand 82 | }; 83 | 84 | 85 | })(); 86 | addSidebarStyles("panels/terminal/style.css?v=3421"); 87 | 88 | -------------------------------------------------------------------------------- /app/views/custom_views.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | 4 | } 5 | 6 | ] 7 | -------------------------------------------------------------------------------- /app/views/views.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "custom_categories", 4 | "description": "Custom Node Categories", 5 | "icon": "⌬", 6 | "panels": [ 7 | "custom_categories" 8 | ] 9 | }, 10 | { 11 | "id": "custom_templates", 12 | "description": "Templates list", 13 | "icon": "Τ", 14 | "panels": [ 15 | "custom_templates" 16 | ] 17 | }, 18 | { 19 | "id": "custom_workflows", 20 | "description": "Workflows list", 21 | "icon": "W", 22 | "panels": [ 23 | "custom_workflows" 24 | ] 25 | }, 26 | { 27 | "id": "section_-281285428", 28 | "description": "Assets Downloader", 29 | "icon": "A", 30 | "panels": [ 31 | "assets_downloader" 32 | ] 33 | }, 34 | { 35 | "id": "section_-1116604772", 36 | "description": "Terminal", 37 | "icon": ">", 38 | "panels": [ 39 | "terminal" 40 | ] 41 | } 42 | ] -------------------------------------------------------------------------------- /app/views/views_default.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "custom_categories", 4 | "description": "Custom Node Categories", 5 | "icon": "⌬", 6 | "panels": [ 7 | "custom_categories" 8 | ] 9 | }, 10 | { 11 | "id": "custom_templates", 12 | "description": "Templates list", 13 | "icon": "Τ", 14 | "panels": [ 15 | "custom_templates" 16 | ] 17 | }, 18 | { 19 | "id": "custom_workflows", 20 | "description": "Workflows list", 21 | "icon": "W", 22 | "panels": [ 23 | "custom_workflows" 24 | ] 25 | }, 26 | { 27 | "id": "assets_downloader", 28 | "description": "Assets Downloader", 29 | "icon": "A", 30 | "panels": [ 31 | "assets_downloader" 32 | ] 33 | }, 34 | { 35 | "id": "section_terminal", 36 | "description": "Terminal", 37 | "icon": ">", 38 | "panels": [ 39 | "terminal" 40 | ] 41 | } 42 | ] -------------------------------------------------------------------------------- /images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/images/.DS_Store -------------------------------------------------------------------------------- /images/YouTube.svg: -------------------------------------------------------------------------------- 1 | YOUTUBEYOUTUBE -------------------------------------------------------------------------------- /images/assets.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/images/assets.gif -------------------------------------------------------------------------------- /images/custom_categories.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/images/custom_categories.gif -------------------------------------------------------------------------------- /images/dd.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/images/dd.gif -------------------------------------------------------------------------------- /images/expand_collapse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/images/expand_collapse.gif -------------------------------------------------------------------------------- /images/fuzzysearch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/images/fuzzysearch.gif -------------------------------------------------------------------------------- /images/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/images/header.png -------------------------------------------------------------------------------- /images/patreon_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/images/patreon_badge.png -------------------------------------------------------------------------------- /images/pin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/images/pin.gif -------------------------------------------------------------------------------- /images/pin_reorder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/images/pin_reorder.gif -------------------------------------------------------------------------------- /images/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/images/preview.gif -------------------------------------------------------------------------------- /images/search_categories.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/images/search_categories.gif -------------------------------------------------------------------------------- /images/search_nodes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/images/search_nodes.gif -------------------------------------------------------------------------------- /images/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/images/settings.png -------------------------------------------------------------------------------- /images/templates.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/images/templates.gif -------------------------------------------------------------------------------- /images/theme.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/images/theme.gif -------------------------------------------------------------------------------- /images/workflows.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nuked88/ComfyUI-N-Sidebar/dff702527de2747fb8d649c6ebfed362df2f340e/images/workflows.gif -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "comfyui-n-sidebar" 3 | description = "A simple sidebar for ComfyUI." 4 | version = "1.6.3" 5 | license = { file = "LICENSE" } 6 | 7 | [project.urls] 8 | Repository = "https://github.com/Nuked88/ComfyUI-N-Sidebar" 9 | # Used by Comfy Registry https://comfyregistry.org 10 | 11 | [tool.comfy] 12 | PublisherId = "nuked" 13 | DisplayName = "ComfyUI-N-Sidebar" 14 | Icon = "" 15 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiofiles --------------------------------------------------------------------------------