style: format code with Prettier and StandardJS#4
style: format code with Prettier and StandardJS#4deepsource-autofix[bot] wants to merge 11 commits intomainfrom
Conversation
- Added main application script for Conjuration, initializing core components such as UI Manager, Theme Manager, and various tools (Brush Engine, Palette Tool, etc.). - Set up event listeners for window controls, menu management, and tool interactions. - Created a centralized MenuManager class to handle menu interactions and state management. - Implemented canvas size selection dialog with visual previews and resizing functionality. - Added project management features including new, open, and save project functionalities. - Integrated GIF and PNG export capabilities.
- Added PixelCanvas class to handle drawing on a canvas with pixel manipulation. - Implemented methods for drawing pixels, lines, rectangles, ellipses, and flood fill. - Introduced undo/redo functionality with history management. - Added support for various visual effects (grain, static, glitch, CRT, scan lines, vignette, noise, pixelate). - Implemented zooming and grid display features. - Included methods for exporting canvas as PNG and managing pixel data. - Set up event listeners for mouse interactions and cursor position updates.
The [optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) operator can be used to perform null checks before accessing a property, or calling a function.
refactor: convert logical operator to optional chainining
This commit fixes the style issues introduced in c83a21c according to the output from Prettier and StandardJS. Details: None
Reviewer's GuideThis PR reformats code across app.js and voidAPI.js to comply with Prettier and StandardJS style guidelines, making purely stylistic adjustments—no functional logic was altered. File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Here's the code health analysis summary for commits Analysis Summary
|
| document.addEventListener("DOMContentLoaded", () => { | ||
| // Initialize UI Manager | ||
| const uiManager = new UIManager(); | ||
|
|
||
| // Initialize Theme Manager | ||
| const themeManager = new ThemeManager(); | ||
|
|
||
| // Add data-text attributes to section titles for glitch effect | ||
| document.querySelectorAll('.section-title').forEach(title => { | ||
| title.setAttribute('data-text', title.textContent); | ||
| document.querySelectorAll(".section-title").forEach((title) => { | ||
| title.setAttribute("data-text", title.textContent); | ||
| }); | ||
|
|
||
| // Initialize Menu System | ||
| const menuSystem = new MenuSystem(); | ||
|
|
||
| // Initialize Canvas with temporary size (will be changed by user selection) | ||
| const pixelCanvas = new PixelCanvas({ | ||
| canvasId: 'pixel-canvas', | ||
| effectsCanvasId: 'effects-canvas', | ||
| uiCanvasId: 'ui-canvas', | ||
| canvasId: "pixel-canvas", | ||
| effectsCanvasId: "effects-canvas", | ||
| uiCanvasId: "ui-canvas", | ||
| width: 64, | ||
| height: 64, | ||
| pixelSize: 8 | ||
| pixelSize: 8, | ||
| }); | ||
|
|
||
| // Show canvas size selection dialog on startup |
There was a problem hiding this comment.
Initialization of Components in DOMContentLoaded Event Listener
The initialization of multiple components such as UIManager, ThemeManager, MenuSystem, and PixelCanvas directly within the DOMContentLoaded event listener can lead to tightly coupled code, which is harder to maintain and test. Consider refactoring this initialization into a separate function or class that handles the setup. This approach would improve modularity and make the codebase more maintainable.
Recommendation:
Create a function like initializeApplicationComponents() that encapsulates all the initialization logic. This function can then be called within the DOMContentLoaded event listener.
| setupAnimationControls(); | ||
| setupZoomControls(); | ||
| setupMiscControls(); | ||
|
|
||
| updateCanvasSizeDisplay(); | ||
| uiManager.setActiveTool('brush-pencil'); | ||
| uiManager.setActiveSymmetry('symmetry-none'); | ||
| uiManager.setActivePalette('palette-monochrome'); | ||
| uiManager.setActiveTool("brush-pencil"); | ||
| uiManager.setActiveSymmetry("symmetry-none"); | ||
| uiManager.setActivePalette("palette-monochrome"); | ||
| } |
There was a problem hiding this comment.
Event Listener Setup Function
The function setupEventListeners is responsible for setting up various event listeners across the application. However, this function is doing too much by handling the setup for multiple unrelated functionalities (window controls, menus, tools, etc.). This violates the Single Responsibility Principle and can make the function difficult to maintain as the application grows.
Recommendation:
Refactor setupEventListeners into multiple smaller functions, each handling a specific aspect of the application. For example, separate functions for setting up window controls, menu interactions, and tool activations would make the code more organized and maintainable.
| // Initialize Canvas with temporary size (will be changed by user selection) | ||
| const pixelCanvas = new PixelCanvas({ | ||
| canvasId: 'pixel-canvas', | ||
| effectsCanvasId: 'effects-canvas', | ||
| uiCanvasId: 'ui-canvas', | ||
| canvasId: "pixel-canvas", | ||
| effectsCanvasId: "effects-canvas", | ||
| uiCanvasId: "ui-canvas", | ||
| width: 64, | ||
| height: 64, | ||
| pixelSize: 8 | ||
| pixelSize: 8, | ||
| }); |
There was a problem hiding this comment.
Initial Canvas Size Optimization
The canvas is initialized with a temporary size of 64x64 pixels, which is then expected to be changed by the user. This approach might not be optimal as it involves initializing with a size that might never be used, wasting resources.
Recommendation:
Consider delaying the initialization of the canvas until the user selects a size, or initialize it with a more commonly used size based on user data. This could improve the application's performance and resource usage.
| voidAPI.openProject().then((result) => { | ||
| if (result.success) { | ||
| try { | ||
| const projectData = result.data; | ||
| pixelCanvas.setDimensions(projectData.width, projectData.height); | ||
| timeline.loadFromData(projectData.frames); | ||
| menuSystem.closeAllMenus(); | ||
| uiManager.showToast('Project loaded successfully', 'success'); | ||
| uiManager.showToast("Project loaded successfully", "success"); | ||
| } catch (error) { | ||
| uiManager.showToast('Failed to load project: ' + error.message, 'error'); | ||
| uiManager.showToast( | ||
| "Failed to load project: " + error.message, | ||
| "error", | ||
| ); | ||
| } | ||
| } | ||
| }); |
There was a problem hiding this comment.
Error Handling in Project Opening
The function handleOpenProject uses promises to handle asynchronous file opening operations. However, it lacks error handling for scenarios where the file cannot be opened or is corrupted.
Recommendation:
Implement a .catch() method on the promise chain to handle errors gracefully. This will improve the application's robustness by informing the user of the issue and preventing the application from becoming unresponsive or crashing.
| setupAnimationControls(); | ||
| setupZoomControls(); | ||
| setupMiscControls(); | ||
|
|
||
| updateCanvasSizeDisplay(); | ||
| uiManager.setActiveTool('brush-pencil'); | ||
| uiManager.setActiveSymmetry('symmetry-none'); | ||
| uiManager.setActivePalette('palette-monochrome'); | ||
| uiManager.setActiveTool("brush-pencil"); | ||
| uiManager.setActiveSymmetry("symmetry-none"); | ||
| uiManager.setActivePalette("palette-monochrome"); | ||
| } | ||
|
|
||
| function setupWindowControls() { | ||
| document.getElementById('minimize-button').addEventListener('click', () => voidAPI.minimizeWindow()); | ||
|
|
||
| document.getElementById('maximize-button').addEventListener('click', () => { | ||
| voidAPI.maximizeWindow().then(result => { | ||
| document.getElementById('maximize-button').textContent = result.isMaximized ? '□' : '[]'; | ||
| document | ||
| .getElementById("minimize-button") | ||
| .addEventListener("click", () => voidAPI.minimizeWindow()); | ||
|
|
||
| document.getElementById("maximize-button").addEventListener("click", () => { | ||
| voidAPI.maximizeWindow().then((result) => { | ||
| document.getElementById("maximize-button").textContent = | ||
| result.isMaximized ? "□" : "[]"; | ||
| }); | ||
| }); | ||
|
|
||
| document.getElementById('close-button').addEventListener('click', () => voidAPI.closeWindow()); | ||
|
|
||
| document | ||
| .getElementById("close-button") | ||
| .addEventListener("click", () => voidAPI.closeWindow()); | ||
| } | ||
|
|
||
| function setupMenuManager() { | ||
| // Already handled by MenuManager | ||
| } | ||
|
|
||
| function setupFileMenu() { | ||
| document.getElementById('new-project').addEventListener('click', handleNewProject); | ||
| document.getElementById('open-project').addEventListener('click', handleOpenProject); | ||
| document.getElementById('save-project').addEventListener('click', handleSaveProject); | ||
| document | ||
| .getElementById("new-project") | ||
| .addEventListener("click", handleNewProject); | ||
| document | ||
| .getElementById("open-project") | ||
| .addEventListener("click", handleOpenProject); | ||
| document | ||
| .getElementById("save-project") | ||
| .addEventListener("click", handleSaveProject); | ||
| } | ||
|
|
||
| function setupEditMenu() { | ||
| document.getElementById('undo').addEventListener('click', handleUndo); | ||
| document.getElementById('redo').addEventListener('click', handleRedo); | ||
| document.getElementById('toggle-grid').addEventListener('click', handleToggleGrid); | ||
| document.getElementById('resize-canvas').addEventListener('click', handleResizeCanvas); | ||
| document.getElementById("undo").addEventListener("click", handleUndo); | ||
| document.getElementById("redo").addEventListener("click", handleRedo); | ||
| document | ||
| .getElementById("toggle-grid") | ||
| .addEventListener("click", handleToggleGrid); | ||
| document | ||
| .getElementById("resize-canvas") | ||
| .addEventListener("click", handleResizeCanvas); | ||
| } | ||
|
|
||
| function setupExportMenu() { | ||
| document.getElementById('export-png').addEventListener('click', handleExportPNG); | ||
| document.getElementById('export-gif').addEventListener('click', handleExportGIF); | ||
| document | ||
| .getElementById("export-png") | ||
| .addEventListener("click", handleExportPNG); | ||
| document | ||
| .getElementById("export-gif") | ||
| .addEventListener("click", handleExportGIF); | ||
| } | ||
|
|
||
| function setupThemeMenu() { | ||
| document.getElementById('theme-lain-dive').addEventListener('click', () => { | ||
| themeManager.setTheme('lain-dive'); | ||
| document.getElementById("theme-lain-dive").addEventListener("click", () => { | ||
| themeManager.setTheme("lain-dive"); | ||
| menuSystem.closeAllMenus(); | ||
| }); | ||
|
|
||
| document.getElementById('theme-morrowind-glyph').addEventListener('click', () => { | ||
| themeManager.setTheme('morrowind-glyph'); | ||
| menuSystem.closeAllMenus(); | ||
| }); | ||
| document | ||
| .getElementById("theme-morrowind-glyph") | ||
| .addEventListener("click", () => { | ||
| themeManager.setTheme("morrowind-glyph"); | ||
| menuSystem.closeAllMenus(); | ||
| }); | ||
|
|
||
| document.getElementById('theme-monolith').addEventListener('click', () => { | ||
| themeManager.setTheme('monolith'); | ||
| document.getElementById("theme-monolith").addEventListener("click", () => { | ||
| themeManager.setTheme("monolith"); | ||
| menuSystem.closeAllMenus(); | ||
| }); | ||
| } | ||
|
|
||
| function setupLoreMenu() { | ||
| document.getElementById('lore-option1').addEventListener('click', () => { | ||
| themeManager.setTheme('lain-dive'); | ||
| document.getElementById("lore-option1").addEventListener("click", () => { | ||
| themeManager.setTheme("lain-dive"); | ||
| menuSystem.closeAllMenus(); | ||
| uiManager.showToast('Lore: Lain Dive activated', 'success'); | ||
| uiManager.showToast("Lore: Lain Dive activated", "success"); | ||
| }); | ||
|
|
||
| document.getElementById('lore-option2').addEventListener('click', () => { | ||
| themeManager.setTheme('morrowind-glyph'); | ||
| document.getElementById("lore-option2").addEventListener("click", () => { | ||
| themeManager.setTheme("morrowind-glyph"); | ||
| menuSystem.closeAllMenus(); | ||
| uiManager.showToast('Lore: Morrowind Glyph activated', 'success'); | ||
| uiManager.showToast("Lore: Morrowind Glyph activated", "success"); | ||
| }); | ||
|
|
||
| document.getElementById('lore-option3').addEventListener('click', () => { | ||
| themeManager.setTheme('monolith'); | ||
| document.getElementById("lore-option3").addEventListener("click", () => { | ||
| themeManager.setTheme("monolith"); | ||
| menuSystem.closeAllMenus(); | ||
| uiManager.showToast('Lore: Monolith activated', 'success'); | ||
| uiManager.showToast("Lore: Monolith activated", "success"); | ||
| }); | ||
| } | ||
|
|
||
| function setupToolButtons() { | ||
| document.querySelectorAll('.tool-button').forEach(button => { | ||
| button.addEventListener('click', () => { | ||
| document.querySelectorAll(".tool-button").forEach((button) => { | ||
| button.addEventListener("click", () => { | ||
| const toolId = button.id; | ||
|
|
||
| if (toolId.startsWith('brush-')) { | ||
| const brushType = toolId.replace('brush-', ''); | ||
| if (toolId.startsWith("brush-")) { | ||
| const brushType = toolId.replace("brush-", ""); | ||
| brushEngine.setActiveBrush(brushType); | ||
| uiManager.setActiveTool(toolId); | ||
| } | ||
|
|
||
| if (toolId.startsWith('symmetry-')) { | ||
| const symmetryType = toolId.replace('symmetry-', ''); | ||
| if (toolId.startsWith("symmetry-")) { | ||
| const symmetryType = toolId.replace("symmetry-", ""); | ||
| symmetryTools.setSymmetryMode(symmetryType); | ||
| uiManager.setActiveSymmetry(toolId); | ||
| } |
There was a problem hiding this comment.
Event Listener Setup in a Loop
The setupToolButtons function (lines 181-198) adds event listeners to elements with the class .tool-button. Each button click triggers a check for the button's ID to determine the action, which involves string operations and conditional checks inside the event listener.
Recommendation:
- Optimize by reducing redundancy: Instead of checking the ID for every click, categorize buttons beforehand and add specific event listeners for each category. This reduces the need for string operations and conditions during runtime, improving performance.
| // Checkerboard | ||
| [ | ||
| [1, 0], | ||
| [0, 1] | ||
| [0, 1], | ||
| ], | ||
| // Diagonal lines | ||
| [ | ||
| [1, 0, 0], | ||
| [0, 1, 0], | ||
| [0, 0, 1] | ||
| [0, 0, 1], | ||
| ], | ||
| // Dots | ||
| [ | ||
| [0, 0, 0], | ||
| [0, 1, 0], | ||
| [0, 0, 0] | ||
| [0, 0, 0], | ||
| ], | ||
| // Cross | ||
| [ | ||
| [0, 1, 0], | ||
| [1, 1, 1], | ||
| [0, 1, 0] | ||
| ] | ||
| [0, 1, 0], | ||
| ], | ||
| ]; | ||
|
|
||
| // Select a pattern based on brush size |
There was a problem hiding this comment.
The applyPatternBrush method exhibits high complexity due to multiple nested loops and conditional checks. This complexity can make the code hard to maintain or modify, especially when adding new patterns or changing brush behavior.
Recommendation: Refactor the method to simplify the logic, possibly by extracting pattern application into a separate function or by using a strategy pattern to handle different brush types more cleanly.
|
|
||
| // Show welcome message | ||
| uiManager.showToast('Welcome to Conjuration', 'success'); | ||
| uiManager.showToast('Welcome to Conjuration', 'success') |
There was a problem hiding this comment.
Issue: Lack of Error Handling and Dependency Checks
The initialization of various managers (UIManager, ThemeManager, etc.) and systems (MenuSystem, PixelCanvas) is done sequentially without any error handling or checks for successful initialization. This could lead to runtime errors if any dependencies fail to load or initialize correctly.
Recommendation:
- Implement error handling around each initialization step. Use try-catch blocks or conditional checks to ensure each component is loaded successfully before proceeding to the next. This will prevent the application from breaking if one part fails to initialize properly.
|
|
||
| // Show welcome message | ||
| uiManager.showToast('Welcome to Conjuration', 'success'); | ||
| uiManager.showToast('Welcome to Conjuration', 'success') |
There was a problem hiding this comment.
Issue: Inefficient DOM Manipulations and Event Listener Management
The application frequently queries the DOM and attaches event listeners within the DOMContentLoaded event. This approach can lead to performance issues and potential memory leaks if the listeners are not managed properly.
Recommendation:
- Optimize DOM access by caching frequently accessed elements in variables.
- Ensure that event listeners are removed appropriately when they are no longer needed or when the associated elements are removed from the DOM. This can be managed through a centralized event handling system or by using more modern frameworks that handle such clean-ups implicitly.
| maximizeWindow: () => | ||
| new Promise((resolve) => { | ||
| ipcRenderer.send('maximize-window') | ||
| ipcRenderer.once('maximize-reply', (_, result) => resolve(result)) | ||
| }), | ||
|
|
||
| closeWindow: () => ipcRenderer.send('close-window'), | ||
|
|
||
| openProject: () => new Promise((resolve) => { | ||
| ipcRenderer.send('open-project'); | ||
| ipcRenderer.once('open-project-reply', (_, result) => resolve(result)); | ||
| }), | ||
|
|
||
| saveProject: (projectData) => new Promise((resolve) => { | ||
| ipcRenderer.send('save-project', projectData); | ||
| ipcRenderer.once('save-project-reply', (_, result) => resolve(result)); | ||
| }), | ||
|
|
||
| exportPng: (dataUrl) => new Promise((resolve) => { | ||
| ipcRenderer.send('export-png', dataUrl); | ||
| ipcRenderer.once('export-png-reply', (_, result) => resolve(result)); | ||
| }), | ||
|
|
||
| exportGif: (gifData) => new Promise((resolve) => { | ||
| ipcRenderer.send('export-gif', gifData); | ||
| ipcRenderer.once('export-gif-reply', (_, result) => resolve(result)); | ||
| }) | ||
| }; | ||
|
|
||
| module.exports = voidAPI; No newline at end of file | ||
|
|
||
| openProject: () => | ||
| new Promise((resolve) => { | ||
| ipcRenderer.send('open-project') | ||
| ipcRenderer.once('open-project-reply', (_, result) => resolve(result)) | ||
| }), | ||
|
|
||
| saveProject: (projectData) => | ||
| new Promise((resolve) => { | ||
| ipcRenderer.send('save-project', projectData) | ||
| ipcRenderer.once('save-project-reply', (_, result) => resolve(result)) | ||
| }), | ||
|
|
||
| exportPng: (dataUrl) => | ||
| new Promise((resolve) => { | ||
| ipcRenderer.send('export-png', dataUrl) | ||
| ipcRenderer.once('export-png-reply', (_, result) => resolve(result)) | ||
| }), | ||
|
|
||
| exportGif: (gifData) => | ||
| new Promise((resolve) => { | ||
| ipcRenderer.send('export-gif', gifData) | ||
| ipcRenderer.once('export-gif-reply', (_, result) => resolve(result)) | ||
| }) |
There was a problem hiding this comment.
Lack of Error Handling in Promise-based IPC Calls
The Promise-based functions (maximizeWindow, openProject, saveProject, exportPng, exportGif) do not handle errors that may occur during IPC communication. This can lead to unresolved Promises if the IPC main process encounters an error or does not respond, affecting the application's reliability and user experience.
Recommendation: Implement error handling by adding rejection conditions and handling IPC errors. For example, you can modify the IPC calls to handle error scenarios by using ipcRenderer.once for error events and rejecting the Promise accordingly:
ipcRenderer.once('maximize-window-error', (_, error) => reject(error));|
|
||
| saveProject: (projectData) => | ||
| new Promise((resolve) => { | ||
| ipcRenderer.send('save-project', projectData) | ||
| ipcRenderer.once('save-project-reply', (_, result) => resolve(result)) | ||
| }), | ||
|
|
||
| exportPng: (dataUrl) => | ||
| new Promise((resolve) => { | ||
| ipcRenderer.send('export-png', dataUrl) | ||
| ipcRenderer.once('export-png-reply', (_, result) => resolve(result)) | ||
| }), | ||
|
|
||
| exportGif: (gifData) => | ||
| new Promise((resolve) => { |
There was a problem hiding this comment.
Security Concerns with User Data in IPC Calls
The functions saveProject and exportGif send user-provided data (projectData and gifData) through IPC without apparent validation or sanitization. This could lead to security vulnerabilities such as IPC event injection or unintended behavior if the data contains malicious structures.
Recommendation: Ensure that all user-provided data is validated and sanitized before being sent through IPC. Implement checks to ensure the data structure conforms to expected schemas and does not contain potentially harmful content:
if (isValidProjectData(projectData)) {
ipcRenderer.send('save-project', projectData);
} else {
throw new Error('Invalid project data');
}| document.addEventListener("DOMContentLoaded", () => { | ||
| // Initialize UI Manager | ||
| const uiManager = new UIManager(); | ||
|
|
||
| // Initialize Theme Manager | ||
| const themeManager = new ThemeManager(); | ||
|
|
||
| // Add data-text attributes to section titles for glitch effect | ||
| document.querySelectorAll('.section-title').forEach(title => { | ||
| title.setAttribute('data-text', title.textContent); | ||
| document.querySelectorAll(".section-title").forEach((title) => { | ||
| title.setAttribute("data-text", title.textContent); | ||
| }); | ||
|
|
||
| // Initialize Menu System | ||
| const menuSystem = new MenuSystem(); | ||
|
|
||
| // Initialize Canvas with temporary size (will be changed by user selection) | ||
| const pixelCanvas = new PixelCanvas({ | ||
| canvasId: 'pixel-canvas', | ||
| effectsCanvasId: 'effects-canvas', | ||
| uiCanvasId: 'ui-canvas', | ||
| canvasId: "pixel-canvas", | ||
| effectsCanvasId: "effects-canvas", | ||
| uiCanvasId: "ui-canvas", | ||
| width: 64, | ||
| height: 64, | ||
| pixelSize: 8 | ||
| pixelSize: 8, | ||
| }); | ||
|
|
||
| // Show canvas size selection dialog on startup |
There was a problem hiding this comment.
Initialization Order Dependency
The initialization order of the components (UIManager, ThemeManager, MenuSystem, PixelCanvas) is critical for the application's correct functioning. If any component relies on the state or methods of another during its initialization, it could lead to runtime errors or undefined behavior if the dependent component is not yet initialized.
Recommendation:
Ensure that no component's initialization depends on the state or methods of another component that is initialized later. Consider implementing a dependency injection pattern or ensuring that all components are fully initialized before they are used.
| setupAnimationControls(); | ||
| setupZoomControls(); | ||
| setupMiscControls(); | ||
|
|
||
| updateCanvasSizeDisplay(); | ||
| uiManager.setActiveTool('brush-pencil'); | ||
| uiManager.setActiveSymmetry('symmetry-none'); | ||
| uiManager.setActivePalette('palette-monochrome'); | ||
| uiManager.setActiveTool("brush-pencil"); | ||
| uiManager.setActiveSymmetry("symmetry-none"); | ||
| uiManager.setActivePalette("palette-monochrome"); | ||
| } | ||
|
|
||
| function setupWindowControls() { | ||
| document.getElementById('minimize-button').addEventListener('click', () => voidAPI.minimizeWindow()); | ||
|
|
||
| document.getElementById('maximize-button').addEventListener('click', () => { | ||
| voidAPI.maximizeWindow().then(result => { | ||
| document.getElementById('maximize-button').textContent = result.isMaximized ? '□' : '[]'; | ||
| document | ||
| .getElementById("minimize-button") | ||
| .addEventListener("click", () => voidAPI.minimizeWindow()); | ||
|
|
||
| document.getElementById("maximize-button").addEventListener("click", () => { | ||
| voidAPI.maximizeWindow().then((result) => { | ||
| document.getElementById("maximize-button").textContent = | ||
| result.isMaximized ? "□" : "[]"; | ||
| }); | ||
| }); | ||
|
|
||
| document.getElementById('close-button').addEventListener('click', () => voidAPI.closeWindow()); | ||
|
|
||
| document | ||
| .getElementById("close-button") | ||
| .addEventListener("click", () => voidAPI.closeWindow()); | ||
| } | ||
|
|
||
| function setupMenuManager() { | ||
| // Already handled by MenuManager | ||
| } | ||
|
|
||
| function setupFileMenu() { | ||
| document.getElementById('new-project').addEventListener('click', handleNewProject); | ||
| document.getElementById('open-project').addEventListener('click', handleOpenProject); | ||
| document.getElementById('save-project').addEventListener('click', handleSaveProject); | ||
| document | ||
| .getElementById("new-project") | ||
| .addEventListener("click", handleNewProject); | ||
| document | ||
| .getElementById("open-project") | ||
| .addEventListener("click", handleOpenProject); | ||
| document | ||
| .getElementById("save-project") | ||
| .addEventListener("click", handleSaveProject); | ||
| } | ||
|
|
||
| function setupEditMenu() { | ||
| document.getElementById('undo').addEventListener('click', handleUndo); | ||
| document.getElementById('redo').addEventListener('click', handleRedo); | ||
| document.getElementById('toggle-grid').addEventListener('click', handleToggleGrid); | ||
| document.getElementById('resize-canvas').addEventListener('click', handleResizeCanvas); | ||
| document.getElementById("undo").addEventListener("click", handleUndo); | ||
| document.getElementById("redo").addEventListener("click", handleRedo); | ||
| document | ||
| .getElementById("toggle-grid") | ||
| .addEventListener("click", handleToggleGrid); | ||
| document | ||
| .getElementById("resize-canvas") | ||
| .addEventListener("click", handleResizeCanvas); | ||
| } | ||
|
|
||
| function setupExportMenu() { | ||
| document.getElementById('export-png').addEventListener('click', handleExportPNG); | ||
| document.getElementById('export-gif').addEventListener('click', handleExportGIF); | ||
| document | ||
| .getElementById("export-png") | ||
| .addEventListener("click", handleExportPNG); | ||
| document | ||
| .getElementById("export-gif") | ||
| .addEventListener("click", handleExportGIF); | ||
| } | ||
|
|
||
| function setupThemeMenu() { | ||
| document.getElementById('theme-lain-dive').addEventListener('click', () => { | ||
| themeManager.setTheme('lain-dive'); | ||
| document.getElementById("theme-lain-dive").addEventListener("click", () => { | ||
| themeManager.setTheme("lain-dive"); | ||
| menuSystem.closeAllMenus(); | ||
| }); | ||
|
|
||
| document.getElementById('theme-morrowind-glyph').addEventListener('click', () => { | ||
| themeManager.setTheme('morrowind-glyph'); | ||
| menuSystem.closeAllMenus(); | ||
| }); | ||
| document | ||
| .getElementById("theme-morrowind-glyph") | ||
| .addEventListener("click", () => { | ||
| themeManager.setTheme("morrowind-glyph"); | ||
| menuSystem.closeAllMenus(); | ||
| }); | ||
|
|
||
| document.getElementById('theme-monolith').addEventListener('click', () => { | ||
| themeManager.setTheme('monolith'); | ||
| document.getElementById("theme-monolith").addEventListener("click", () => { | ||
| themeManager.setTheme("monolith"); | ||
| menuSystem.closeAllMenus(); | ||
| }); | ||
| } | ||
|
|
||
| function setupLoreMenu() { | ||
| document.getElementById('lore-option1').addEventListener('click', () => { | ||
| themeManager.setTheme('lain-dive'); | ||
| document.getElementById("lore-option1").addEventListener("click", () => { | ||
| themeManager.setTheme("lain-dive"); | ||
| menuSystem.closeAllMenus(); | ||
| uiManager.showToast('Lore: Lain Dive activated', 'success'); | ||
| uiManager.showToast("Lore: Lain Dive activated", "success"); | ||
| }); | ||
|
|
||
| document.getElementById('lore-option2').addEventListener('click', () => { | ||
| themeManager.setTheme('morrowind-glyph'); | ||
| document.getElementById("lore-option2").addEventListener("click", () => { | ||
| themeManager.setTheme("morrowind-glyph"); | ||
| menuSystem.closeAllMenus(); | ||
| uiManager.showToast('Lore: Morrowind Glyph activated', 'success'); | ||
| uiManager.showToast("Lore: Morrowind Glyph activated", "success"); | ||
| }); | ||
|
|
||
| document.getElementById('lore-option3').addEventListener('click', () => { | ||
| themeManager.setTheme('monolith'); | ||
| document.getElementById("lore-option3").addEventListener("click", () => { | ||
| themeManager.setTheme("monolith"); | ||
| menuSystem.closeAllMenus(); | ||
| uiManager.showToast('Lore: Monolith activated', 'success'); | ||
| uiManager.showToast("Lore: Monolith activated", "success"); | ||
| }); | ||
| } | ||
|
|
||
| function setupToolButtons() { | ||
| document.querySelectorAll('.tool-button').forEach(button => { | ||
| button.addEventListener('click', () => { | ||
| document.querySelectorAll(".tool-button").forEach((button) => { | ||
| button.addEventListener("click", () => { | ||
| const toolId = button.id; | ||
|
|
||
| if (toolId.startsWith('brush-')) { | ||
| const brushType = toolId.replace('brush-', ''); | ||
| if (toolId.startsWith("brush-")) { | ||
| const brushType = toolId.replace("brush-", ""); | ||
| brushEngine.setActiveBrush(brushType); | ||
| uiManager.setActiveTool(toolId); | ||
| } | ||
|
|
||
| if (toolId.startsWith('symmetry-')) { | ||
| const symmetryType = toolId.replace('symmetry-', ''); | ||
| if (toolId.startsWith("symmetry-")) { | ||
| const symmetryType = toolId.replace("symmetry-", ""); | ||
| symmetryTools.setSymmetryMode(symmetryType); | ||
| uiManager.setActiveSymmetry(toolId); | ||
| } |
There was a problem hiding this comment.
Potential Memory Leak in Event Listeners
The setup functions for various UI controls add event listeners but do not provide a mechanism to remove these listeners when they are no longer needed or when the UI components are destroyed. This can lead to memory leaks, especially if these components are frequently created and destroyed during the application's lifecycle.
Recommendation:
Implement a cleanup function that removes all event listeners added by the setup functions. This function should be called whenever the UI components are destroyed or re-initialized. Use named functions for event listeners to make them easier to remove.
| voidAPI.openProject().then((result) => { | ||
| if (result.success) { | ||
| try { | ||
| const projectData = result.data; | ||
| pixelCanvas.setDimensions(projectData.width, projectData.height); | ||
| timeline.loadFromData(projectData.frames); | ||
| menuSystem.closeAllMenus(); | ||
| uiManager.showToast('Project loaded successfully', 'success'); | ||
| uiManager.showToast("Project loaded successfully", "success"); | ||
| } catch (error) { | ||
| uiManager.showToast('Failed to load project: ' + error.message, 'error'); | ||
| uiManager.showToast( | ||
| "Failed to load project: " + error.message, | ||
| "error", | ||
| ); | ||
| } | ||
| } | ||
| }); |
There was a problem hiding this comment.
Error Handling in Asynchronous Operations
The function handleOpenProject uses asynchronous operations to open a project file and update the UI based on the result. However, the error handling is minimal and does not cover scenarios where the openProject function might fail before reaching the .then block, which could leave the application in an inconsistent state.
Recommendation:
- Wrap the
voidAPI.openProject()call within atry-catchblock to handle potential rejections from the promise and ensure that the application can gracefully handle these errors.
| frames: timeline.getFramesData(), | ||
| palette: paletteTool.getCurrentPalette(), | ||
| effects: { | ||
| grain: document.getElementById('effect-grain').checked, | ||
| static: document.getElementById('effect-static').checked, | ||
| glitch: document.getElementById('effect-glitch').checked, | ||
| crt: document.getElementById('effect-crt').checked, | ||
| intensity: document.getElementById('effect-intensity').value | ||
| } | ||
| grain: document.getElementById("effect-grain").checked, | ||
| static: document.getElementById("effect-static").checked, | ||
| glitch: document.getElementById("effect-glitch").checked, | ||
| crt: document.getElementById("effect-crt").checked, | ||
| intensity: document.getElementById("effect-intensity").value, | ||
| }, | ||
| }; | ||
|
|
||
| voidAPI.saveProject(projectData).then(result => { | ||
| voidAPI.saveProject(projectData).then((result) => { | ||
| if (result.success) { | ||
| menuSystem.closeAllMenus(); | ||
| uiManager.showToast('Project saved successfully', 'success'); | ||
| uiManager.showToast("Project saved successfully", "success"); | ||
| } else { | ||
| uiManager.showToast('Failed to save project', 'error'); | ||
| uiManager.showToast("Failed to save project", "error"); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Potential Issue with Asynchronous Error Handling
The function voidAPI.saveProject is used to save project data asynchronously. However, the error handling is only done using a success flag from the response, and there's no .catch() method for handling possible promise rejections, which could lead to unhandled promise rejections if the API call fails at a network level or due to other reasons.
Recommendation:
Add a .catch() method to handle potential errors from the promise, ensuring that all possible failures are gracefully handled and reported to the user. This will improve the robustness of the application's error handling strategy.
Example:
voidAPI.saveProject(projectData).then((result) => {
if (result.success) {
uiManager.showToast("Project saved successfully", "success");
} else {
uiManager.showToast("Failed to save project", "error");
}
}).catch((error) => {
uiManager.showToast("Error saving project: " + error.message, "error");
});| </div> | ||
| `; | ||
|
|
||
| uiManager.showModal('Resize Canvas', content, () => menuSystem.closeAllMenus()); | ||
| uiManager.showModal("Resize Canvas", content, () => | ||
| menuSystem.closeAllMenus(), | ||
| ); | ||
|
|
||
| document.querySelectorAll('.preset-size-button').forEach(button => { | ||
| button.addEventListener('click', () => { | ||
| document.querySelectorAll(".preset-size-button").forEach((button) => { | ||
| button.addEventListener("click", () => { | ||
| const width = parseInt(button.dataset.width); | ||
| const height = parseInt(button.dataset.height); | ||
| document.getElementById('canvas-width').value = width; | ||
| document.getElementById('canvas-height').value = height; | ||
| document.getElementById("canvas-width").value = width; | ||
| document.getElementById("canvas-height").value = height; | ||
| }); | ||
| }); | ||
|
|
||
| const modalFooter = document.createElement('div'); | ||
| modalFooter.className = 'modal-footer'; | ||
| const modalFooter = document.createElement("div"); | ||
| modalFooter.className = "modal-footer"; | ||
|
|
||
| const cancelButton = document.createElement('button'); | ||
| cancelButton.className = 'modal-button'; | ||
| cancelButton.textContent = 'Cancel'; | ||
| cancelButton.addEventListener('click', () => uiManager.hideModal()); | ||
| const cancelButton = document.createElement("button"); | ||
| cancelButton.className = "modal-button"; | ||
| cancelButton.textContent = "Cancel"; | ||
| cancelButton.addEventListener("click", () => uiManager.hideModal()); | ||
|
|
||
| const resizeButton = document.createElement('button'); | ||
| resizeButton.className = 'modal-button primary'; | ||
| resizeButton.textContent = 'Resize'; | ||
| resizeButton.addEventListener('click', () => { | ||
| const width = parseInt(document.getElementById('canvas-width').value); | ||
| const height = parseInt(document.getElementById('canvas-height').value); | ||
| const preserveContent = document.getElementById('preserve-content').checked; | ||
| const resizeButton = document.createElement("button"); | ||
| resizeButton.className = "modal-button primary"; | ||
| resizeButton.textContent = "Resize"; | ||
| resizeButton.addEventListener("click", () => { | ||
| const width = parseInt(document.getElementById("canvas-width").value); | ||
| const height = parseInt(document.getElementById("canvas-height").value); | ||
| const preserveContent = | ||
| document.getElementById("preserve-content").checked; | ||
|
|
||
| if (width > 0 && height > 0 && width <= 1024 && height <= 1024) { | ||
| pixelCanvas.resize(width, height, preserveContent); | ||
| updateCanvasSizeDisplay(); | ||
| uiManager.hideModal(); | ||
| uiManager.showToast(`Canvas resized to ${width}×${height}`, 'success'); | ||
| uiManager.showToast(`Canvas resized to ${width}×${height}`, "success"); | ||
| } else { | ||
| uiManager.showToast('Invalid dimensions', 'error'); | ||
| uiManager.showToast("Invalid dimensions", "error"); | ||
| } | ||
| }); | ||
|
|
||
| modalFooter.appendChild(cancelButton); | ||
| modalFooter.appendChild(resizeButton); | ||
| document.querySelector('.modal-dialog').appendChild(modalFooter); | ||
| document.querySelector(".modal-dialog").appendChild(modalFooter); | ||
| menuSystem.closeAllMenus(); | ||
| } |
There was a problem hiding this comment.
Inefficient DOM Manipulation and Event Handling
The function handleResizeCanvas dynamically creates modal content and buttons every time it is called. This approach can lead to inefficient DOM manipulation and potential memory leaks if event listeners are not properly managed. Each call to this function creates new DOM elements and attaches event listeners to them, which can accumulate over time if not properly removed.
Recommendation:
Consider creating the modal content and buttons once and reusing them across calls. This can be achieved by checking if the elements already exist before creating new ones, and by properly removing event listeners when the modal is closed or no longer needed. This change will enhance the performance by reducing the number of DOM operations and preventing potential memory leaks.
Example:
if (!document.querySelector('.modal-footer')) {
// Create and append modal footer and buttons
}
// Setup event listeners
// Reuse existing elements and update values as needed| if (distance > lineOffset) continue; | ||
|
|
||
| // Get pattern value with proper centering | ||
| const patternX = (px + j + patternOffsetX + patternSize) % patternSize; | ||
| const patternY = (py + k + patternOffsetY + patternSize) % patternSize; | ||
| const patternX = | ||
| (px + j + patternOffsetX + patternSize) % patternSize; | ||
| const patternY = | ||
| (py + k + patternOffsetY + patternSize) % patternSize; | ||
|
|
||
| // Draw the pixel based on pattern | ||
| if (pattern[patternY][patternX]) { |
There was a problem hiding this comment.
Error Handling and Boundary Check
The pattern brush drawing logic does not handle potential errors or undefined behavior when accessing array indices that might be out of bounds. This could occur if the calculated patternX or patternY exceeds the dimensions of the pattern array due to incorrect modulo operations or negative indices.
Suggested Change:
Ensure that the indices patternX and patternY are always within the valid range of the pattern array dimensions by using proper modulo operations and handling negative indices correctly.
const patternX = Math.abs((px + j + patternSize) % patternSize);
const patternY = Math.abs((py + k + patternSize) % patternSize);| function handleExportPNG () { | ||
| const pngDataUrl = pixelCanvas.exportToPNG() | ||
| try { | ||
| voidAPI.exportPng(pngDataUrl).then(result => { | ||
| if (result.success) { | ||
| menuSystem.closeAllMenus(); | ||
| uiManager.showToast('PNG exported successfully', 'success'); | ||
| } else { | ||
| uiManager.showToast('Failed to export PNG', 'error'); | ||
| } | ||
| }).catch(error => { | ||
| voidAPI.exportPng(pngDataUrl).then(result => { | ||
| if (result.success) { | ||
| menuSystem.closeAllMenus(); | ||
| uiManager.showToast('PNG exported successfully', 'success'); | ||
| } else { | ||
| uiManager.showToast('Failed to export PNG', 'error'); | ||
| } | ||
| }).catch(error => { | ||
| uiManager.showToast(`PNG export failed: ${error.message}`, 'error'); | ||
| }); | ||
| uiManager.showToast(`PNG export failed: ${error.message}`, 'error'); | ||
| }); | ||
| voidAPI | ||
| .exportPng(pngDataUrl) | ||
| .then((result) => { | ||
| if (result.success) { | ||
| menuSystem.closeAllMenus() | ||
| uiManager.showToast('PNG exported successfully', 'success') | ||
| } else { | ||
| uiManager.showToast('Failed to export PNG', 'error') | ||
| } | ||
| }) | ||
| .catch((error) => { | ||
| voidAPI | ||
| .exportPng(pngDataUrl) | ||
| .then((result) => { | ||
| if (result.success) { | ||
| menuSystem.closeAllMenus() | ||
| uiManager.showToast('PNG exported successfully', 'success') | ||
| } else { | ||
| uiManager.showToast('Failed to export PNG', 'error') | ||
| } | ||
| }) | ||
| .catch((error) => { | ||
| uiManager.showToast( | ||
| `PNG export failed: ${error.message}`, | ||
| 'error' | ||
| ) | ||
| }) | ||
| uiManager.showToast(`PNG export failed: ${error.message}`, 'error') | ||
| }) | ||
| } catch (error) { | ||
| uiManager.showToast(`PNG export error: ${error.message}`, 'error'); | ||
| uiManager.showToast(`PNG export error: ${error.message}`, 'error') | ||
| } |
There was a problem hiding this comment.
The handleExportPNG function attempts to retry the export operation in the catch block if an error occurs. This approach can lead to potential infinite loops if the error persists. Instead of retrying immediately, consider implementing a more robust error handling strategy. This could include logging the error, notifying the user of the failure, and potentially providing a limited number of retry attempts with a delay or different approach if feasible.
| function handleExportGIF () { | ||
| uiManager.showLoadingDialog('Generating GIF...') | ||
| const frameDelay = parseInt(document.getElementById('frame-delay').value) | ||
| try { | ||
| gifExporter | ||
| .generateGif(frameDelay) | ||
| .then((gifData) => { | ||
| voidAPI | ||
| .exportGif(gifData) | ||
| .then((result) => { | ||
| uiManager.hideLoadingDialog() | ||
| if (result.success) { | ||
| menuSystem.closeAllMenus() | ||
| uiManager.showToast('GIF exported successfully', 'success') | ||
| } else { | ||
| uiManager.showToast('Failed to export GIF', 'error') | ||
| } | ||
| }) | ||
| .catch((error) => { | ||
| uiManager.hideLoadingDialog() | ||
| uiManager.showToast( | ||
| `GIF export failed: ${error.message}`, | ||
| 'error' | ||
| ) | ||
| }) | ||
| }) | ||
| .catch((error) => { | ||
| uiManager.hideLoadingDialog() | ||
| uiManager.showToast( | ||
| `GIF generation failed: ${error.message}`, | ||
| 'error' | ||
| ) | ||
| }) | ||
| } catch (error) { | ||
| uiManager.hideLoadingDialog(); | ||
| uiManager.showToast(`GIF export error: ${error.message}`, 'error'); | ||
| uiManager.hideLoadingDialog() | ||
| uiManager.showToast(`GIF export error: ${error.message}`, 'error') | ||
| } | ||
| try { | ||
| gifExporter.generateGif(frameDelay).then(gifData => { | ||
| voidAPI.exportGif(gifData).then(result => { | ||
| uiManager.hideLoadingDialog(); | ||
| if (result.success) { | ||
| menuSystem.closeAllMenus(); | ||
| uiManager.showToast('GIF exported successfully', 'success'); | ||
| } else { | ||
| uiManager.showToast('Failed to export GIF', 'error'); | ||
| } | ||
| }).catch(error => { | ||
| uiManager.hideLoadingDialog(); | ||
| uiManager.showToast(`GIF export failed: ${error.message}`, 'error'); | ||
| }); | ||
| }).catch(error => { | ||
| uiManager.hideLoadingDialog(); | ||
| uiManager.showToast(`GIF generation failed: ${error.message}`, 'error'); | ||
| }); | ||
| try { | ||
| gifExporter | ||
| .generateGif(frameDelay) | ||
| .then((gifData) => { | ||
| voidAPI | ||
| .exportGif(gifData) | ||
| .then((result) => { | ||
| uiManager.hideLoadingDialog() | ||
| if (result.success) { | ||
| menuSystem.closeAllMenus() | ||
| uiManager.showToast('GIF exported successfully', 'success') | ||
| } else { | ||
| uiManager.showToast('Failed to export GIF', 'error') | ||
| } | ||
| }) | ||
| .catch((error) => { | ||
| uiManager.hideLoadingDialog() | ||
| uiManager.showToast( | ||
| `GIF export failed: ${error.message}`, | ||
| 'error' | ||
| ) | ||
| }) | ||
| }) | ||
| .catch((error) => { | ||
| uiManager.hideLoadingDialog() | ||
| uiManager.showToast( | ||
| `GIF generation failed: ${error.message}`, | ||
| 'error' | ||
| ) | ||
| }) | ||
| } catch (error) { | ||
| uiManager.hideLoadingDialog(); | ||
| uiManager.showToast(`GIF export error: ${error.message}`, 'error'); | ||
| uiManager.hideLoadingDialog() | ||
| uiManager.showToast(`GIF export error: ${error.message}`, 'error') | ||
| } | ||
|
|
||
| try { | ||
| gifExporter.generateGif(frameDelay).then(gifData => { | ||
| voidAPI.exportGif(gifData).then(result => { | ||
| uiManager.hideLoadingDialog(); | ||
| if (result.success) { | ||
| menuSystem.closeAllMenus(); | ||
| uiManager.showToast('GIF exported successfully', 'success'); | ||
| } else { | ||
| uiManager.showToast('Failed to export GIF', 'error'); | ||
| } | ||
| }).catch(error => { | ||
| uiManager.hideLoadingDialog(); | ||
| uiManager.showToast(`GIF export failed: ${error.message}`, 'error'); | ||
| }); | ||
| }).catch(error => { | ||
| uiManager.hideLoadingDialog(); | ||
| uiManager.showToast(`GIF generation failed: ${error.message}`, 'error'); | ||
| }); | ||
| gifExporter | ||
| .generateGif(frameDelay) | ||
| .then((gifData) => { | ||
| voidAPI | ||
| .exportGif(gifData) | ||
| .then((result) => { | ||
| uiManager.hideLoadingDialog() | ||
| if (result.success) { | ||
| menuSystem.closeAllMenus() | ||
| uiManager.showToast('GIF exported successfully', 'success') | ||
| } else { | ||
| uiManager.showToast('Failed to export GIF', 'error') | ||
| } | ||
| }) | ||
| .catch((error) => { | ||
| uiManager.hideLoadingDialog() | ||
| uiManager.showToast( | ||
| `GIF export failed: ${error.message}`, | ||
| 'error' | ||
| ) | ||
| }) | ||
| }) | ||
| .catch((error) => { | ||
| uiManager.hideLoadingDialog() | ||
| uiManager.showToast( | ||
| `GIF generation failed: ${error.message}`, | ||
| 'error' | ||
| ) | ||
| }) | ||
| } catch (error) { | ||
| uiManager.hideLoadingDialog(); | ||
| uiManager.showToast(`GIF export error: ${error.message}`, 'error'); | ||
| uiManager.hideLoadingDialog() | ||
| uiManager.showToast(`GIF export error: ${error.message}`, 'error') | ||
| } |
There was a problem hiding this comment.
The handleExportGIF function contains repeated blocks of code for exporting GIFs, which not only bloats the codebase but also introduces potential performance issues. Refactor this function to eliminate redundancy and improve code maintainability. Consider abstracting the common functionality into a separate function or using a loop to handle retries, which would make the code cleaner and easier to manage.
| maximizeWindow: () => | ||
| new Promise((resolve) => { | ||
| ipcRenderer.send('maximize-window') | ||
| ipcRenderer.once('maximize-reply', (_, result) => resolve(result)) | ||
| }), | ||
|
|
||
| closeWindow: () => ipcRenderer.send('close-window'), | ||
|
|
||
| openProject: () => new Promise((resolve) => { | ||
| ipcRenderer.send('open-project'); | ||
| ipcRenderer.once('open-project-reply', (_, result) => resolve(result)); | ||
| }), | ||
|
|
||
| saveProject: (projectData) => new Promise((resolve) => { | ||
| ipcRenderer.send('save-project', projectData); | ||
| ipcRenderer.once('save-project-reply', (_, result) => resolve(result)); | ||
| }), | ||
|
|
||
| exportPng: (dataUrl) => new Promise((resolve) => { | ||
| ipcRenderer.send('export-png', dataUrl); | ||
| ipcRenderer.once('export-png-reply', (_, result) => resolve(result)); | ||
| }), | ||
|
|
||
| exportGif: (gifData) => new Promise((resolve) => { | ||
| ipcRenderer.send('export-gif', gifData); | ||
| ipcRenderer.once('export-gif-reply', (_, result) => resolve(result)); | ||
| }) | ||
| }; | ||
|
|
||
| module.exports = voidAPI; No newline at end of file | ||
|
|
||
| openProject: () => | ||
| new Promise((resolve) => { | ||
| ipcRenderer.send('open-project') | ||
| ipcRenderer.once('open-project-reply', (_, result) => resolve(result)) | ||
| }), | ||
|
|
||
| saveProject: (projectData) => | ||
| new Promise((resolve) => { | ||
| ipcRenderer.send('save-project', projectData) | ||
| ipcRenderer.once('save-project-reply', (_, result) => resolve(result)) | ||
| }), | ||
|
|
||
| exportPng: (dataUrl) => | ||
| new Promise((resolve) => { | ||
| ipcRenderer.send('export-png', dataUrl) | ||
| ipcRenderer.once('export-png-reply', (_, result) => resolve(result)) | ||
| }), | ||
|
|
||
| exportGif: (gifData) => | ||
| new Promise((resolve) => { | ||
| ipcRenderer.send('export-gif', gifData) | ||
| ipcRenderer.once('export-gif-reply', (_, result) => resolve(result)) |
There was a problem hiding this comment.
The methods maximizeWindow, openProject, saveProject, exportPng, and exportGif use promises to handle IPC communication but do not include error handling mechanisms. This can lead to unhandled promise rejections if the main process fails to send a reply or encounters an error.
Recommended Solution:
Implement error handling by adding rejection scenarios in the promises. For example, you can use a timeout to reject the promise if no reply is received within a certain timeframe, and handle potential errors sent from the main process:
new Promise((resolve, reject) => {
ipcRenderer.send('maximize-window');
ipcRenderer.once('maximize-reply', (_, result) => resolve(result));
setTimeout(() => reject(new Error('Timeout waiting for maximize reply')), 5000);
});| maximizeWindow: () => | ||
| new Promise((resolve) => { | ||
| ipcRenderer.send('maximize-window') | ||
| ipcRenderer.once('maximize-reply', (_, result) => resolve(result)) | ||
| }), | ||
|
|
||
| closeWindow: () => ipcRenderer.send('close-window'), | ||
|
|
||
| openProject: () => new Promise((resolve) => { | ||
| ipcRenderer.send('open-project'); | ||
| ipcRenderer.once('open-project-reply', (_, result) => resolve(result)); | ||
| }), | ||
|
|
||
| saveProject: (projectData) => new Promise((resolve) => { | ||
| ipcRenderer.send('save-project', projectData); | ||
| ipcRenderer.once('save-project-reply', (_, result) => resolve(result)); | ||
| }), | ||
|
|
||
| exportPng: (dataUrl) => new Promise((resolve) => { | ||
| ipcRenderer.send('export-png', dataUrl); | ||
| ipcRenderer.once('export-png-reply', (_, result) => resolve(result)); | ||
| }), | ||
|
|
||
| exportGif: (gifData) => new Promise((resolve) => { | ||
| ipcRenderer.send('export-gif', gifData); | ||
| ipcRenderer.once('export-gif-reply', (_, result) => resolve(result)); | ||
| }) | ||
| }; | ||
|
|
||
| module.exports = voidAPI; No newline at end of file | ||
|
|
||
| openProject: () => | ||
| new Promise((resolve) => { | ||
| ipcRenderer.send('open-project') | ||
| ipcRenderer.once('open-project-reply', (_, result) => resolve(result)) | ||
| }), | ||
|
|
||
| saveProject: (projectData) => | ||
| new Promise((resolve) => { | ||
| ipcRenderer.send('save-project', projectData) | ||
| ipcRenderer.once('save-project-reply', (_, result) => resolve(result)) | ||
| }), | ||
|
|
||
| exportPng: (dataUrl) => | ||
| new Promise((resolve) => { | ||
| ipcRenderer.send('export-png', dataUrl) | ||
| ipcRenderer.once('export-png-reply', (_, result) => resolve(result)) | ||
| }), | ||
|
|
||
| exportGif: (gifData) => | ||
| new Promise((resolve) => { | ||
| ipcRenderer.send('export-gif', gifData) |
There was a problem hiding this comment.
The use of ipcRenderer.once to register event listeners for IPC replies without ensuring that these listeners are removed when no reply is received could lead to memory leaks. This is particularly concerning in scenarios where the main process might fail to send a reply, leaving the event listeners hanging indefinitely.
Recommended Solution:
Ensure that event listeners are removed appropriately by adding a cleanup mechanism. This could be implemented using a timeout to remove the listener if no reply is received within a reasonable timeframe:
new Promise((resolve, reject) => {
const handler = (_, result) => {
ipcRenderer.removeListener('maximize-reply', handler);
resolve(result);
};
ipcRenderer.once('maximize-reply', handler);
setTimeout(() => {
ipcRenderer.removeListener('maximize-reply', handler);
reject(new Error('Timeout waiting for maximize reply'));
}, 5000);
});
This commit fixes the style issues introduced in c83a21c according to the output
from Prettier and StandardJS.
Details: None
Summary by Sourcery
Format JavaScript source files with Prettier and StandardJS to enforce consistent styling across the application
Enhancements: