Skip to content

style: format code with Prettier and StandardJS#5

Open
deepsource-autofix[bot] wants to merge 12 commits intomainfrom
deepsource-transform-027226bb
Open

style: format code with Prettier and StandardJS#5
deepsource-autofix[bot] wants to merge 12 commits intomainfrom
deepsource-transform-027226bb

Conversation

@deepsource-autofix
Copy link
Contributor

@deepsource-autofix deepsource-autofix bot commented Jun 24, 2025

This commit fixes the style issues introduced in 3638d87 according to the output
from Prettier and StandardJS.

Details: None

Summary by Sourcery

Apply Prettier and StandardJS formatting across the codebase to standardize quotes, indentation, trailing commas, and whitespace in scripts/app.js and main.js.

Chores:

  • Reformat all string literals from single to double quotes
  • Add missing trailing commas and adjust spacing for object literals and function calls
  • Normalize indentation and line breaks to match style guidelines

numbpill3d and others added 12 commits June 2, 2025 16:10
- 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.
This commit fixes the style issues introduced in 814a7ee according to the output
from Prettier and StandardJS.

Details: #2
refactor: convert logical operator to optional chainining
This commit fixes the style issues introduced in 3638d87 according to the output
from Prettier and StandardJS.

Details: None
@sourcery-ai
Copy link

sourcery-ai bot commented Jun 24, 2025

Reviewer's Guide

Reformatted codebase to comply with Prettier and StandardJS conventions, focusing on consistent quotes, indentation, trailing commas, and spacing.

File-Level Changes

Change Details Files
Applied Prettier and StandardJS formatting across codebase
  • Unified string quotes to double quotes
  • Enforced 2-space indentation throughout
  • Added trailing commas in multiline objects, arrays, and arguments
  • Standardized spacing around arrows, parentheses, and braces
  • Wrapped or aligned long lines to conform to style rules
scripts/app.js
main.js

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@deepsource-io
Copy link
Contributor

deepsource-io bot commented Jun 24, 2025

Here's the code health analysis summary for commits 3638d87..cd651f2. View details on DeepSource ↗.

Analysis Summary

AnalyzerStatusSummaryLink
DeepSource JavaScript LogoJavaScript❌ Failure
❗ 56 occurences introduced
🎯 56 occurences resolved
View Check ↗

💡 If you’re a repository administrator, you can configure the quality gates from the settings.

Comment on lines +142 to 146
const data = fs.readFileSync(filePaths[0], "utf8");
return { success: true, data: JSON.parse(data), filePath: filePaths[0] };
} catch (error) {
return { success: false, error: error.message };
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of synchronous file operations (fs.readFileSync in line 142) can block the main thread, leading to performance issues, especially with large files or slow file systems.

Recommendation: Replace synchronous file operations with their asynchronous counterparts (fs.readFile) to improve the application's performance and responsiveness. This change would help prevent the UI from freezing during file operations.

Comment on lines +11 to +15
// Expose app instance to window for Electron IPC
window.voidApp = {
hasUnsavedChanges: () => app.hasUnsavedChanges(),
saveProject: () => app.saveProject(),
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exposing the application instance globally on the window object can lead to security risks, especially if the application is used in a web environment. Malicious scripts can potentially access and manipulate the application state.

Recommendation: Consider using more secure ways to handle IPC or state sharing, such as using more encapsulated, secure postMessage handlers or dedicated secure Electron IPC methods that do not expose direct references to application internals.

Comment on lines +242 to 375
async openProject() {
try {
// Check for unsaved changes first
if (this.state.isModified) {
const { response } = await this.uiManager.showConfirmDialog(
"Unsaved Changes",
"Do you want to save your changes before opening another project?",
["Save", "Don't Save", "Cancel"],
);

if (response === 0) {
// Save
const saveResult = await this.saveProject();
if (!saveResult.success) {
return; // Don't proceed if save failed
}
} else if (response === 2) {
// Cancel
return;
}

// If response is 1 (Don't Save), proceed with opening
}

// Show loading indicator
const progressToast = this.uiManager.showToast(
"Opening project...",
"info",
0,
);

// Use Electron IPC to open a file dialog
const result = await window.voidAPI.openProject();

if (result.success && result.data) {
try {
// Show saving indicator
const progressToast = this.uiManager.showToast('Preparing project data...', 'info', 0);

// Save current frame first
this.timeline.frames[this.timeline.currentFrameIndex].setImageData(
this.pixelCanvas.getCanvasImageData()
);

// Prepare project data
const projectData = this.prepareProjectData();

if (!projectData || !projectData.frames || projectData.frames.length === 0) {
throw new Error('Invalid project data');
}

progressToast.updateMessage('Saving project file...');

// Use Electron IPC to save the file
const result = await window.voidAPI.saveProject(projectData);

if (result.success) {
progressToast.close();
this.clearModified();
this.uiManager.showToast('Project saved successfully', 'success');
return { success: true };
} else {
throw new Error(result.error || 'Failed to save project file');
}
} catch (error) {
console.error('Error saving project:', error);
this.uiManager.showToast(`Failed to save project: ${error.message}`, 'error', 5000);

// Log detailed error for debugging
if (error.stack) {
console.error('Stack trace:', error.stack);
// Parse the project data
const projectData = result.data;

// Validate project data
if (
!projectData ||
!projectData.frames ||
!Array.isArray(projectData.frames)
) {
throw new Error("Invalid project file format");
}

progressToast.updateMessage("Loading canvas size...");

// Load canvas size
if (projectData.canvasSize) {
if (
!projectData.canvasSize.width ||
!projectData.canvasSize.height ||
projectData.canvasSize.width <= 0 ||
projectData.canvasSize.height <= 0
) {
throw new Error("Invalid canvas dimensions");
}

return { success: false, error: error.message };
}
}

async saveProjectAs() {
try {
// Show saving indicator
const progressToast = this.uiManager.showToast('Preparing project data...', 'info', 0);

// Save current frame first
this.timeline.frames[this.timeline.currentFrameIndex].setImageData(
this.pixelCanvas.getCanvasImageData()

this.pixelCanvas.setCanvasSize(
projectData.canvasSize.width,
projectData.canvasSize.height,
);

// Prepare project data
const projectData = this.prepareProjectData();

if (!projectData || !projectData.frames || projectData.frames.length === 0) {
throw new Error('Invalid project data');
}

progressToast.updateMessage('Saving project file...');

// Use Electron IPC to save the file with dialog
const result = await window.voidAPI.saveProject(projectData);

if (result.success) {
// Update project state
this.state.currentFilePath = result.filePath;
this.state.projectName = this.getFileNameFromPath(result.filePath);
this.clearModified();
this.updateWindowTitle();

progressToast.close();
this.uiManager.showToast('Project saved successfully', 'success');
return { success: true };
} else {
throw new Error(result.error || 'Failed to save project file');
}
} catch (error) {
console.error('Error saving project:', error);
this.uiManager.showToast(`Failed to save project: ${error.message}`, 'error', 5000);

// Log detailed error for debugging
if (error.stack) {
console.error('Stack trace:', error.stack);
}

return { success: false, error: error.message };
}
}

prepareProjectData() {
// Create a complete project data object
return {
appVersion: '0.1.0',
timestamp: new Date().toISOString(),
canvasSize: {
width: this.pixelCanvas.width,
height: this.pixelCanvas.height
},
frames: this.timeline.getFramesData(),
palette: this.paletteTool.currentPalette,
effects: this.effectsEngine.getEffectsSettings(),
metadata: this.state.metadata,
loreLayer: this.state.loreLayer
};
}

getFileNameFromPath(filePath) {
if (!filePath) return 'Untitled';

// Extract file name from path
const pathParts = filePath.split(/[/\\]/);
let fileName = pathParts[pathParts.length - 1];

// Remove extension
const extensionIndex = fileName.lastIndexOf('.');
if (extensionIndex > 0) {
fileName = fileName.substring(0, extensionIndex);
}

return fileName;
}

async exportPNG() {
try {
// Show loading indicator
const progressToast = this.uiManager.showToast('Preparing PNG export...', 'info', 0);

// Get canvas data URL
const dataUrl = this.pixelCanvas.getCanvasData();

if (!dataUrl || !dataUrl.startsWith('data:image/png;base64,')) {
throw new Error('Invalid PNG data generated');
}

progressToast.updateMessage('Saving PNG file...');

// Use Electron IPC to save the PNG
const result = await window.voidAPI.exportPng(dataUrl);

if (result.success) {
progressToast.close();
this.uiManager.showToast('PNG exported successfully', 'success');
} else {
throw new Error(result.error || 'Failed to save PNG file');
}
} catch (error) {
console.error('Error exporting PNG:', error);
this.uiManager.showToast(`Failed to export PNG: ${error.message}`, 'error', 5000);

// Log detailed error for debugging
if (error.stack) {
console.error('Stack trace:', error.stack);
}
}
}

async exportGIF() {
try {
// Show options dialog for GIF export
this.uiManager.showModal('Export GIF', this.createGifOptionsForm());
} catch (error) {
console.error('Error showing GIF export dialog:', error);
this.uiManager.showToast('Failed to open GIF export options: ' + error.message, 'error');
}
}

createGifOptionsForm() {
const form = document.createElement('div');
form.className = 'gif-options-form';

// Loop options
const loopGroup = document.createElement('div');
loopGroup.className = 'form-group';

const loopLabel = document.createElement('label');
loopLabel.textContent = 'Loop Count:';

const loopInput = document.createElement('input');
loopInput.type = 'number';
loopInput.id = 'gif-loop-count';
loopInput.min = '0';
loopInput.max = '100';
loopInput.value = '0';

const loopHelp = document.createElement('small');
loopHelp.textContent = '0 = infinite loop';

loopGroup.appendChild(loopLabel);
loopGroup.appendChild(loopInput);
loopGroup.appendChild(loopHelp);

// Quality options
const qualityGroup = document.createElement('div');
qualityGroup.className = 'form-group';

const qualityLabel = document.createElement('label');
qualityLabel.textContent = 'Quality:';

const qualitySlider = document.createElement('input');
qualitySlider.type = 'range';
qualitySlider.id = 'gif-quality';
qualitySlider.min = '1';
qualitySlider.max = '20';
qualitySlider.value = '10';

const qualityValue = document.createElement('span');
qualityValue.id = 'quality-value';
qualityValue.textContent = '10';

qualitySlider.addEventListener('input', () => {
qualityValue.textContent = qualitySlider.value;
});

qualityGroup.appendChild(qualityLabel);
qualityGroup.appendChild(qualitySlider);
qualityGroup.appendChild(qualityValue);

// Dithering option
const ditherGroup = document.createElement('div');
ditherGroup.className = 'form-group';

const ditherLabel = document.createElement('label');
ditherLabel.textContent = 'Apply Dithering:';

const ditherCheckbox = document.createElement('input');
ditherCheckbox.type = 'checkbox';
ditherCheckbox.id = 'gif-dither';

ditherGroup.appendChild(ditherLabel);
ditherGroup.appendChild(ditherCheckbox);

// Buttons
const buttonGroup = document.createElement('div');
buttonGroup.className = 'form-buttons';

const exportButton = document.createElement('button');
exportButton.type = 'button';
exportButton.className = 'save-button';
exportButton.textContent = 'Export GIF';
exportButton.addEventListener('click', () => {
this.uiManager.closeModal();

// Get options
const options = {
quality: parseInt(qualitySlider.value),
dither: ditherCheckbox.checked,
loop: parseInt(loopInput.value)
};

this.processGifExport(options);
});

const cancelButton = document.createElement('button');
cancelButton.type = 'button';
cancelButton.className = 'cancel-button';
cancelButton.textContent = 'Cancel';
cancelButton.addEventListener('click', () => {
this.uiManager.closeModal();
});

buttonGroup.appendChild(exportButton);
buttonGroup.appendChild(cancelButton);

// Assemble form
form.appendChild(loopGroup);
form.appendChild(qualityGroup);
form.appendChild(ditherGroup);
form.appendChild(buttonGroup);

return form;
}

async processGifExport(options) {
try {
// Show loading indicator with progress
const progressToast = this.uiManager.showToast('Preparing frames for GIF export...', 'info', 0);

// Save current frame first to ensure it's included
this.timeline.frames[this.timeline.currentFrameIndex].setImageData(
this.pixelCanvas.getCanvasImageData()
}

progressToast.updateMessage("Loading frames...");

// Load frames
if (projectData.frames && Array.isArray(projectData.frames)) {
await this.timeline.setFramesFromData(
projectData.frames,
(progress) => {
progressToast.updateMessage(
`Loading frames... ${Math.round(progress * 100)}%`,
);
},
);
// Validate frames
if (this.timeline.frames.length === 0) {
throw new Error('No frames to export');
}
// Update progress
progressToast.updateMessage('Generating GIF...');
// Create GIF with options and progress callback
const gifData = await this.gifExporter.exportGif(options, (progress) => {
progressToast.updateMessage(`Generating GIF... ${Math.round(progress * 100)}%`);
});

if (!gifData || gifData.length === 0) {
throw new Error('Failed to generate GIF data');
}
// Update progress
progressToast.updateMessage('Saving GIF file...');

// Use Electron IPC to save the GIF
const result = await window.voidAPI.exportGif(gifData);
if (result.success) {
// Close progress toast
progressToast.close();
this.uiManager.showToast('GIF exported successfully', 'success');
} else {
throw new Error(result.error || 'Failed to save GIF file');
}
} catch (error) {
console.error('Error exporting GIF:', error);
this.uiManager.showToast(`Failed to export GIF: ${error.message}`, 'error', 5000);
// Log detailed error for debugging
if (error.stack) {
console.error('Stack trace:', error.stack);
}
}

progressToast.updateMessage("Loading palette...");

// Load palette
if (projectData.palette) {
this.paletteTool.setPalette(projectData.palette);
}

progressToast.updateMessage("Loading effects...");

// Load effects
if (projectData.effects) {
this.effectsEngine.setEffectsSettings(projectData.effects);
}

progressToast.updateMessage("Loading metadata...");

// Load metadata
if (projectData.metadata) {
this.state.metadata = projectData.metadata;
}

// Load lore layer
if (projectData.loreLayer) {
this.state.loreLayer = projectData.loreLayer;
}

// Update project state
this.state.currentFilePath = result.filePath;
this.state.projectName = this.getFileNameFromPath(result.filePath);
this.clearModified();

progressToast.close();
this.uiManager.showToast("Project loaded successfully", "success");
} catch (parseError) {
throw new Error(
`Failed to parse project file: ${parseError.message}`,
);
}
} else {
throw new Error(result.error || "No file selected");
}
} catch (error) {
console.error("Error opening project:", error);
this.uiManager.showToast(
`Failed to open project: ${error.message}`,
"error",
5000,
);

// Log detailed error for debugging
if (error.stack) {
console.error("Stack trace:", error.stack);
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The openProject function involves multiple asynchronous operations and interacts with external APIs through IPC. While there is basic error handling in place, the function could benefit from more comprehensive error handling strategies to improve robustness and user experience.

Recommendation: Implement finer-grained error handling and recovery strategies. For instance, ensure that all potential failure points in the asynchronous flow are caught and handled appropriately. Use finally blocks where necessary to clean up resources, like closing any open dialogs or progress indicators, regardless of success or failure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant