├── .gitattributes ├── .github └── workflows │ └── publish.yml ├── .gitignore ├── __init__.py ├── minimap.js ├── pyproject.toml ├── readme.md ├── screenshot.png └── size_position.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Comfy registry 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "pyproject.toml" 9 | 10 | jobs: 11 | publish-node: 12 | name: Publish Custom Node to registry 13 | runs-on: ubuntu-latest 14 | # if this is a forked repository. Skipping the workflow. 15 | if: github.event.repository.fork == false 16 | steps: 17 | - name: Check out code 18 | uses: actions/checkout@v4 19 | - name: Publish Custom Node 20 | uses: Comfy-Org/publish-node-action@main 21 | with: 22 | ## Add your own personal access token to your Github Repository secrets and reference it here. 23 | personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # __init__.py 2 | 3 | # Specify the directory where your JavaScript files are located 4 | WEB_DIRECTORY = "." 5 | 6 | # Optionally, you can also define node class mappings and display name mappings 7 | NODE_CLASS_MAPPINGS = {} 8 | NODE_DISPLAY_NAME_MAPPINGS = {} 9 | 10 | # These need to be included in the __all__ list so that ComfyUI recognizes them 11 | __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"] -------------------------------------------------------------------------------- /minimap.js: -------------------------------------------------------------------------------- 1 | import { api } from '../../scripts/api.js'; 2 | 3 | console.log("Graph Mirroring Script Loaded"); 4 | const nodeTitleHeight = 30; 5 | let currentExecutingNode = "0"; 6 | 7 | // Function to create and inject the mini-graph canvas into the DOM 8 | function createMiniGraphCanvas(settings) { 9 | const miniGraphDiv = document.createElement('div'); 10 | miniGraphDiv.id = 'minimap'; 11 | miniGraphDiv.style.position = 'absolute'; 12 | miniGraphDiv.style.top = `${settings.top}px`; 13 | miniGraphDiv.style.left = `${settings.left}px`; 14 | miniGraphDiv.style.width = `${settings.width}px`; 15 | miniGraphDiv.style.height = `${settings.height}px`; 16 | miniGraphDiv.style.border = '1px solid var(--border-color)'; 17 | miniGraphDiv.style.backgroundColor = 'var(--bg-color)'; 18 | miniGraphDiv.style.zIndex = 1000; 19 | 20 | document.body.appendChild(miniGraphDiv); 21 | 22 | const miniGraphCanvas = document.createElement('canvas'); 23 | miniGraphCanvas.width = settings.width; 24 | miniGraphCanvas.height = settings.height; 25 | miniGraphDiv.appendChild(miniGraphCanvas); 26 | 27 | return miniGraphCanvas; 28 | } 29 | 30 | function getTypeColor(link) { 31 | const type = link.type; 32 | let color = app.canvas.default_connection_color_byType[type] 33 | if (color == "") { 34 | switch (type) { 35 | case "STRING": 36 | case "INT": 37 | color = "#77ff77"; 38 | break; 39 | default: 40 | color = "#666"; 41 | if (link.color != undefined) { 42 | color = link.color; 43 | } 44 | break; 45 | } 46 | } 47 | return color; 48 | } 49 | 50 | function getLinkPosition(originNode, targetNode, bounds, link, scale) { 51 | const xOffset = 10; 52 | const topPadding = 15 * scale; // Space for node title 53 | const linkPadding = 20 * scale; // Space between inputs 54 | 55 | function calculateX(node, isOrigin) { 56 | let nodeWidth = node.size[0]; 57 | 58 | if (node.flags?.collapsed) { 59 | nodeWidth = node._collapsed_width; 60 | } 61 | 62 | const nodeX = node.pos[0] + (isOrigin ? nodeWidth - xOffset : xOffset); 63 | return (nodeX - bounds.left) * scale; 64 | } 65 | 66 | function calculateY(node, slot) { 67 | const baseY = (node.pos[1] - bounds.top) * scale; 68 | 69 | if (node.flags?.collapsed) { 70 | return baseY - nodeTitleHeight * 0.5 * scale; 71 | } 72 | if (node.isVirtualNode) { 73 | return baseY + node.size[1] * 0.5 * scale; 74 | } 75 | 76 | return baseY + topPadding + slot * linkPadding; 77 | } 78 | 79 | const originX = calculateX(originNode, true); 80 | const targetX = calculateX(targetNode, false); 81 | 82 | const originY = calculateY(originNode, link.origin_slot); 83 | const targetY = calculateY(targetNode, link.target_slot); 84 | 85 | return [originX, originY, targetX, targetY]; 86 | } 87 | 88 | function drawDot(ctx, x, y, color, scale) { 89 | ctx.beginPath(); 90 | ctx.arc(x, y, 3 * scale, 0, Math.PI * 2); 91 | ctx.fillStyle = color; 92 | ctx.fill(); 93 | } 94 | 95 | // Function to render the graph onto the mini-graph canvas 96 | function renderMiniGraph(graph, miniGraphCanvas) { 97 | const rootStyles = getComputedStyle(document.documentElement); 98 | const defaultNodeColor = rootStyles.getPropertyValue('--comfy-menu-bg').trim(); 99 | 100 | const ctx = miniGraphCanvas.getContext('2d'); 101 | 102 | // Get the background color of the workflow 103 | const canvasElement = document.querySelector('canvas'); 104 | const backgroundColor = getComputedStyle(canvasElement).backgroundColor; 105 | 106 | // Clear the entire mini-graph canvas 107 | ctx.clearRect(0, 0, miniGraphCanvas.width, miniGraphCanvas.height); 108 | 109 | // Fill the canvas with the background color 110 | ctx.fillStyle = backgroundColor; 111 | ctx.fillRect(0, 0, miniGraphCanvas.width, miniGraphCanvas.height); 112 | 113 | // Calculate the scale based on the graph bounds 114 | const bounds = getGraphBounds(graph); 115 | const scaleX = miniGraphCanvas.width / (bounds.width + 200); // Add padding for better visualization 116 | const scaleY = miniGraphCanvas.height / (bounds.height + 200); 117 | const scale = Math.min(scaleX, scaleY); 118 | 119 | // Draw connections (links) between nodes first 120 | graph.links.forEach(link => { 121 | const originNode = graph._nodes_by_id[link.origin_id]; 122 | const targetNode = graph._nodes_by_id[link.target_id]; 123 | 124 | if (originNode && targetNode) { 125 | ctx.strokeStyle = getTypeColor(link); 126 | ctx.lineWidth = 0.5; 127 | 128 | // Correctly calculate positions for the connections 129 | const [originX, originY, targetX, targetY] = getLinkPosition(originNode, targetNode, bounds, link, scale); 130 | 131 | ctx.beginPath(); 132 | ctx.moveTo(originX, originY); 133 | ctx.lineTo(targetX, targetY); 134 | ctx.stroke(); 135 | 136 | // Store the coordinates for the dots 137 | link._originPos = { x: originX, y: originY }; 138 | link._targetPos = { x: targetX, y: targetY }; 139 | } 140 | }); 141 | 142 | // Render groups (if any) 143 | graph._groups.forEach(group => { 144 | ctx.fillStyle = group.color || '#ccc'; // Use group color or default 145 | ctx.globalAlpha = 0.35; 146 | const x = (group.pos[0] - bounds.left) * scale; 147 | const y = (group.pos[1] - bounds.top) * scale; 148 | const width = group.size[0] * scale; 149 | const height = group.size[1] * scale; 150 | ctx.fillRect(x, y, width, height); 151 | ctx.globalAlpha = 1.0; 152 | }); 153 | 154 | // Render nodes on top of the connections 155 | graph._nodes.forEach(node => { 156 | const nodeColor = node.color || defaultNodeColor; 157 | // For some reason, the top title of the nodes are not included in the size. 158 | let heightPadding = node.isVirtualNode ? 0 : nodeTitleHeight; 159 | 160 | ctx.fillStyle = nodeColor; 161 | 162 | // Scale the node position and size to fit the mini-graph canvas 163 | const x = (node.pos[0] - bounds.left) * scale; 164 | const y = (node.pos[1] - bounds.top - heightPadding) * scale; 165 | let width = node.size[0] * scale; 166 | let height = (node.size[1] + heightPadding) * scale; 167 | 168 | // Override width and height if collapsed 169 | if (node.flags?.collapsed) { 170 | width = node._collapsed_width * scale; 171 | height = nodeTitleHeight * scale; 172 | } 173 | 174 | ctx.fillRect(x, y, width, height); 175 | 176 | if (node.id == currentExecutingNode) { 177 | ctx.strokeStyle = 'green'; 178 | ctx.lineWidth = 1; 179 | 180 | // Draw the outline 181 | ctx.strokeRect(x, y, width, height); 182 | } 183 | }); 184 | 185 | // Draw all the dots on top 186 | if (scale > 0.15) { 187 | const drawnCoordinates = new Set(); 188 | 189 | graph.links.forEach(link => { 190 | if (link._originPos && link._targetPos) { 191 | const dotColor = getTypeColor(link); 192 | 193 | const originKey = `${link._originPos.x},${link._originPos.y}`; 194 | const targetKey = `${link._targetPos.x},${link._targetPos.y}`; 195 | 196 | if (!drawnCoordinates.has(originKey)) { 197 | drawDot(ctx, link._originPos.x, link._originPos.y, dotColor, scale); 198 | drawnCoordinates.add(originKey); 199 | } 200 | 201 | if (!drawnCoordinates.has(targetKey)) { 202 | drawDot(ctx, link._targetPos.x, link._targetPos.y, dotColor, scale); 203 | drawnCoordinates.add(targetKey); 204 | } 205 | } 206 | }); 207 | } 208 | 209 | // Draw the viewport rectangle 210 | drawViewportRectangle(ctx, bounds, scale); 211 | 212 | // Store scale and bounds for click handling 213 | miniGraphCanvas.scale = scale; 214 | miniGraphCanvas.bounds = bounds; 215 | } 216 | 217 | // Function to draw the viewport rectangle 218 | function drawViewportRectangle(ctx, bounds, scale) { 219 | const canvasElement = document.querySelector('canvas'); 220 | const viewportWidth = canvasElement.clientWidth / window.app.canvas.ds.scale; 221 | const viewportHeight = canvasElement.clientHeight / window.app.canvas.ds.scale; 222 | const offsetX = -window.app.canvas.ds.offset[0]; 223 | const offsetY = -window.app.canvas.ds.offset[1]; 224 | 225 | const x = (offsetX - bounds.left) * scale; 226 | const y = (offsetY - bounds.top) * scale; 227 | const width = viewportWidth * scale; 228 | const height = viewportHeight * scale; 229 | 230 | ctx.strokeStyle = 'rgba(168, 219, 235, 0.5)'; 231 | ctx.lineWidth = 1; 232 | ctx.strokeRect(x, y, width, height); 233 | } 234 | 235 | // Function to calculate the bounds of the graph 236 | function getGraphBounds(graph) { 237 | let minX = Infinity; 238 | let minY = Infinity; 239 | let maxX = -Infinity; 240 | let maxY = -Infinity; 241 | 242 | graph._nodes.forEach(node => { 243 | if (node.pos[0] < minX) minX = node.pos[0]; 244 | if (node.pos[1] < minY) minY = node.pos[1]; 245 | if (node.pos[0] + node.size[0] > maxX) maxX = node.pos[0] + node.size[0]; 246 | if (node.pos[1] + node.size[1] > maxY) maxY = node.pos[1] + node.size[1]; 247 | }); 248 | 249 | // Include group bounds if groups exist 250 | graph._groups.forEach(group => { 251 | if (group.pos[0] < minX) minX = group.pos[0]; 252 | if (group.pos[1] < minY) minY = group.pos[1]; 253 | if (group.pos[0] + group.size[0] > maxX) maxX = group.pos[0] + group.size[0]; 254 | if (group.pos[1] + group.size[1] > maxY) maxY = group.pos[1] + group.size[1]; 255 | }); 256 | 257 | return { 258 | left: minX, 259 | top: minY, 260 | width: maxX - minX, 261 | height: maxY - minY 262 | }; 263 | } 264 | 265 | // Function to move the main canvas based on the mouse event 266 | function moveMainCanvas(event, miniGraphCanvas) { 267 | const rect = miniGraphCanvas.getBoundingClientRect(); 268 | const clickX = event.clientX - rect.left; 269 | const clickY = event.clientY - rect.top; 270 | 271 | const graphX = clickX / miniGraphCanvas.scale + miniGraphCanvas.bounds.left; 272 | const graphY = clickY / miniGraphCanvas.scale + miniGraphCanvas.bounds.top; 273 | 274 | // Center the main canvas around the clicked point 275 | const canvasElement = document.querySelector('canvas'); 276 | const viewportWidth = canvasElement.clientWidth / window.app.canvas.ds.scale; 277 | const viewportHeight = canvasElement.clientHeight / window.app.canvas.ds.scale; 278 | 279 | window.app.canvas.ds.offset[0] = -(graphX - viewportWidth / 2); 280 | window.app.canvas.ds.offset[1] = -(graphY - viewportHeight / 2); 281 | 282 | window.app.canvas.setDirty(true, true); // Force redraw 283 | } 284 | 285 | // Function to initialize the mini-graph and start the rendering loop 286 | function initializeMiniGraph(settings) { 287 | const miniGraphCanvas = createMiniGraphCanvas(settings); 288 | let isDragging = false; 289 | 290 | function updateMiniGraph() { 291 | renderMiniGraph(window.app.graph, miniGraphCanvas); 292 | } 293 | 294 | // Handle mouse down event 295 | miniGraphCanvas.addEventListener('mousedown', function(event) { 296 | if (event.ctrlKey) { 297 | return; // Do nothing if Ctrl is pressed 298 | } 299 | 300 | isDragging = true; 301 | moveMainCanvas(event, miniGraphCanvas); 302 | }); 303 | 304 | // Handle mouse move event (for dragging) 305 | miniGraphCanvas.addEventListener('mousemove', function(event) { 306 | if (isDragging) { 307 | moveMainCanvas(event, miniGraphCanvas); 308 | } 309 | }); 310 | 311 | // Handle mouse up event (stop dragging) 312 | miniGraphCanvas.addEventListener('mouseup', function() { 313 | isDragging = false; 314 | }); 315 | 316 | // Handle mouse out event (stop dragging if mouse leaves the minimap) 317 | miniGraphCanvas.addEventListener('mouseout', function() { 318 | isDragging = false; 319 | }); 320 | 321 | // Update the mini-graph immediately and then on every frame 322 | updateMiniGraph(); 323 | setInterval(updateMiniGraph, 100); // Adjust the interval as needed 324 | } 325 | 326 | // Ensure the app and graph are ready before initializing the mini-graph 327 | function waitForAppAndGraph() { 328 | const interval = setInterval(() => { 329 | if (window.app && window.app.graph && window.app.graph._nodes && window.app.graph._nodes.length > 0) { 330 | clearInterval(interval); // Stop checking once the app and graph are ready 331 | 332 | // Load settings from localStorage (or use defaults) 333 | const settings = JSON.parse(localStorage.getItem('minimapSettings')) || { 334 | top: window.innerHeight - 140, // Start from bottom 335 | left: window.innerWidth - 240, // Start from right 336 | width: 240, 337 | height: 140, 338 | opacity: 1 339 | }; 340 | 341 | api.addEventListener("executing", (e) => { 342 | const nodeId = e.detail; 343 | if (nodeId != null) { 344 | currentExecutingNode = nodeId; 345 | return; 346 | } 347 | currentExecutingNode = 0; 348 | }); 349 | 350 | initializeMiniGraph(settings); // Start the mini-graph with loaded settings 351 | } 352 | }, 500); // Check every 500ms 353 | } 354 | 355 | // Start the waiting process 356 | waitForAppAndGraph(); 357 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "comfyui-minimap" 3 | description = "This is the initial build of a comfyui minimap.\nVery simple so far. It makes a copy of the workflow graph, simplified and minified, in the bottom left hand corner. It puts a red border where your viewport is. Left click on the minimap and it will move your viewport to that location." 4 | version = "1.0.0" 5 | license = {file = "LICENSE"} 6 | 7 | [project.urls] 8 | Repository = "https://github.com/OliverCrosby/Comfyui-Minimap" 9 | # Used by Comfy Registry https://comfyregistry.org 10 | 11 | [tool.comfy] 12 | PublisherId = "oliver" 13 | DisplayName = "Comfyui-Minimap" 14 | Icon = "" 15 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Comfyui Minimap 2 | 3 | A minimap of your Comfyui workflow that you can use to navigate. 4 | 5 | ## Position and Size 6 | - Resize or move by holding ctrl/command then click and drag. 7 | - Change the opacity by holding ctrl/command and scrolling. 8 | 9 | ## Install 10 | Install using Comfyui manager, or create a new folder in custom_nodes and add this repository to it. 11 | 12 | ![screenshot of the minimap](screenshot.png) -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OliverCrosby/Comfyui-Minimap/dad932d8affcf341e0cbed28fb6b9f1c9e83a043/screenshot.png -------------------------------------------------------------------------------- /size_position.js: -------------------------------------------------------------------------------- 1 | // Load interact.js from CDN 2 | const interactScript = document.createElement('script'); 3 | interactScript.src = 'https://cdn.jsdelivr.net/npm/interactjs/dist/interact.min.js'; 4 | document.head.appendChild(interactScript); 5 | 6 | interactScript.onload = () => { 7 | let isCtrlPressed = false; 8 | let resizeTimeout; 9 | 10 | // Listen for keydown and keyup events to track the state of the Ctrl key 11 | window.addEventListener('keydown', (event) => { 12 | if (event.key === 'Control') { 13 | isCtrlPressed = true; 14 | } 15 | }); 16 | 17 | window.addEventListener('keyup', (event) => { 18 | if (event.key === 'Control') { 19 | isCtrlPressed = false; 20 | } 21 | }); 22 | 23 | // Function to save minimap settings to local storage, including opacity 24 | function saveMinimapSettings(top, left, width, height, opacity) { 25 | const settings = { 26 | top: parseInt(top, 10), 27 | left: parseInt(left, 10), 28 | width: parseInt(width, 10), 29 | height: parseInt(height, 10), 30 | opacity: opacity 31 | }; 32 | localStorage.setItem('minimapSettings', JSON.stringify(settings)); 33 | } 34 | 35 | // Function to load minimap settings from local storage 36 | function loadMinimapSettings() { 37 | const settings = localStorage.getItem('minimapSettings'); 38 | return settings ? JSON.parse(settings) : null; 39 | } 40 | 41 | // Apply the loaded settings 42 | function applyMinimapSettings(miniMapElement, settings) { 43 | if (settings) { 44 | miniMapElement.style.top = `${settings.top}px`; 45 | miniMapElement.style.left = `${settings.left}px`; 46 | miniMapElement.style.width = `${settings.width}px`; 47 | miniMapElement.style.height = `${settings.height}px`; 48 | miniMapElement.style.opacity = settings.opacity; 49 | } 50 | } 51 | 52 | // Wait for the #minimap to be injected into the DOM 53 | function waitForMinimap() { 54 | const interval = setInterval(() => { 55 | const miniMapElement = document.getElementById('minimap'); 56 | if (miniMapElement) { 57 | clearInterval(interval); 58 | const settings = loadMinimapSettings(); 59 | applyMinimapSettings(miniMapElement, settings); 60 | makeDraggable(miniMapElement); 61 | makeResizable(miniMapElement); 62 | makeScrollable(miniMapElement); // Add scroll handling for opacity 63 | ensureMinimapInBounds(miniMapElement); // Make sure minimap is in bounds 64 | 65 | window.addEventListener('resize', function() { 66 | if (resizeTimeout) { 67 | clearTimeout(resizeTimeout); 68 | } 69 | 70 | resizeTimeout = setTimeout(function() { 71 | ensureMinimapInBounds(miniMapElement); // Ensure minimap stays within the window 72 | }, 200); 73 | }); 74 | } 75 | }, 500); 76 | } 77 | 78 | // Function to make the #minimap draggable and confined within the window 79 | function makeDraggable(miniMapElement) { 80 | const position = { x: 0, y: 0 }; 81 | 82 | miniMapElement.classList.add('draggable'); 83 | 84 | interact('#minimap').draggable({ 85 | listeners: { 86 | start(event) { 87 | if (!isCtrlPressed) { 88 | return event.interaction.stop(); // Prevent dragging if Ctrl is not held down 89 | } 90 | position.x = parseFloat(miniMapElement.style.left) || 0; 91 | position.y = parseFloat(miniMapElement.style.top) || 0; 92 | }, 93 | move(event) { 94 | position.x += event.dx; 95 | position.y += event.dy; 96 | 97 | const windowWidth = window.innerWidth - miniMapElement.offsetWidth; 98 | const windowHeight = window.innerHeight - miniMapElement.offsetHeight; 99 | 100 | position.x = Math.max(0, Math.min(position.x, windowWidth)); 101 | position.y = Math.max(0, Math.min(position.y, windowHeight)); 102 | 103 | miniMapElement.style.left = `${position.x}px`; 104 | miniMapElement.style.top = `${position.y}px`; 105 | 106 | // Save the new position to the settings 107 | const opacity = parseFloat(miniMapElement.style.opacity) || 1; 108 | saveMinimapSettings(position.y, position.x, miniMapElement.offsetWidth, miniMapElement.offsetHeight, opacity); 109 | } 110 | }, 111 | cursorChecker(action) { 112 | // Only show the move cursor if Ctrl is pressed 113 | if (isCtrlPressed) { 114 | return 'move'; 115 | } 116 | return null; 117 | } 118 | }); 119 | } 120 | 121 | // Function to make the #minimap resizable 122 | function makeResizable(miniMapElement) { 123 | interact('#minimap').resizable({ 124 | edges: { left: true, right: true, bottom: true, top: true }, 125 | listeners: { 126 | start(event) { 127 | if (!isCtrlPressed) { 128 | return event.interaction.stop(); // Prevent resizing if Ctrl is not held down 129 | } 130 | }, 131 | move(event) { 132 | let { x, y } = event.target.getBoundingClientRect(); 133 | 134 | const { width, height } = event.rect; 135 | 136 | // Adjust position when resizing from the left or top edges 137 | if (event.edges.left) { 138 | x += event.deltaRect.left; 139 | miniMapElement.style.left = `${x}px`; 140 | } 141 | if (event.edges.top) { 142 | y += event.deltaRect.top; 143 | miniMapElement.style.top = `${y}px`; 144 | } 145 | 146 | miniMapElement.style.width = `${width}px`; 147 | miniMapElement.style.height = `${height}px`; 148 | 149 | // Update the size of the canvas inside the minimap 150 | const miniGraphCanvas = miniMapElement.querySelector('canvas'); 151 | miniGraphCanvas.width = width; 152 | miniGraphCanvas.height = height; 153 | 154 | // Save the new size and position to the settings 155 | const opacity = parseFloat(miniMapElement.style.opacity) || 1; 156 | saveMinimapSettings(miniMapElement.style.top, miniMapElement.style.left, width, height, opacity); 157 | } 158 | }, 159 | modifiers: [ 160 | interact.modifiers.restrictSize({ 161 | min: { width: 100, height: 60 }, 162 | max: { width: window.innerWidth, height: window.innerHeight } 163 | }) 164 | ], 165 | cursorChecker(action, interactable, element, interacting) { 166 | // Show appropriate resize cursor based on the edge being interacted with 167 | if (isCtrlPressed) { 168 | if ((action.edges.left && action.edges.top) || (action.edges.right && action.edges.bottom)) { 169 | return 'nwse-resize'; // Top-left or bottom-right corners 170 | } else if ((action.edges.right && action.edges.top) || (action.edges.left && action.edges.bottom)) { 171 | return 'nesw-resize'; // Top-right or bottom-left corners 172 | } else if (action.edges.left || action.edges.right) { 173 | return 'ew-resize'; // Horizontal resize 174 | } else if (action.edges.top || action.edges.bottom) { 175 | return 'ns-resize'; // Vertical resize 176 | } 177 | } 178 | return null; 179 | } 180 | 181 | }); 182 | } 183 | 184 | // Function to handle scrolling for opacity change 185 | function makeScrollable(miniMapElement) { 186 | miniMapElement.addEventListener('wheel', function(event) { 187 | if (isCtrlPressed) { 188 | event.preventDefault(); // Prevent default scroll action 189 | let opacity = parseFloat(miniMapElement.style.opacity) || 1; 190 | opacity += event.deltaY * -0.001; // Adjust the opacity based on scroll direction 191 | opacity = Math.min(Math.max(opacity, 0.1), 1); // Constrain opacity between 0.1 and 1 192 | miniMapElement.style.opacity = opacity; 193 | 194 | // Save the new opacity to the settings 195 | const top = parseFloat(miniMapElement.style.top); 196 | const left = parseFloat(miniMapElement.style.left); 197 | const width = parseFloat(miniMapElement.style.width); 198 | const height = parseFloat(miniMapElement.style.height); 199 | saveMinimapSettings(top, left, width, height, opacity); 200 | } 201 | }); 202 | } 203 | 204 | function ensureMinimapInBounds(miniMapElement) { 205 | const windowWidth = window.innerWidth; 206 | const windowHeight = window.innerHeight; 207 | 208 | const miniMapRect = miniMapElement.getBoundingClientRect(); 209 | 210 | let newTop = parseFloat(miniMapElement.style.top); 211 | let newLeft = parseFloat(miniMapElement.style.left); 212 | 213 | // Ensure the minimap is not out of bounds on any side 214 | if (miniMapRect.top < 0) { 215 | newTop = 0; 216 | } else if (miniMapRect.bottom > windowHeight) { 217 | newTop = windowHeight - miniMapRect.height; 218 | } 219 | 220 | if (miniMapRect.left < 0) { 221 | newLeft = 0; 222 | } else if (miniMapRect.right > windowWidth) { 223 | newLeft = windowWidth - miniMapRect.width; 224 | } 225 | 226 | miniMapElement.style.top = `${newTop}px`; 227 | miniMapElement.style.left = `${newLeft}px`; 228 | 229 | // Save the settings after adjusting the position 230 | const width = parseFloat(miniMapElement.style.width); 231 | const height = parseFloat(miniMapElement.style.height); 232 | const opacity = parseFloat(miniMapElement.style.opacity) || 1; 233 | saveMinimapSettings(newTop, newLeft, width, height, opacity); 234 | } 235 | 236 | waitForMinimap(); 237 | }; 238 | --------------------------------------------------------------------------------