Refactor utility functions and IndexedDB handling

Extracted and centralized common utility functions such as createCanvas, normalizeToUint8, and generateUniqueFileName in CommonUtils.js to reduce code duplication. Added createImageFromSource to ImageUtils.js. Refactored db.js to use a helper for IndexedDB requests, simplifying error handling and reducing boilerplate. Updated CanvasIO.js, CanvasLayers.js, and Canvas_view.js to use the new utilities.
This commit is contained in:
Dariusz L
2025-06-26 01:58:18 +02:00
parent 7fe34e940e
commit df2680f2c9
6 changed files with 144 additions and 176 deletions

View File

@@ -1,5 +1,7 @@
import {saveImage, getImage, removeImage} from "./db.js"; import {saveImage, getImage, removeImage} from "./db.js";
import {logger, LogLevel} from "./logger.js"; import {logger, LogLevel} from "./logger.js";
import {createCanvas, normalizeToUint8} from "./CommonUtils.js";
import {createImageFromSource} from "./ImageUtils.js";
// Inicjalizacja loggera dla modułu CanvasIO // Inicjalizacja loggera dla modułu CanvasIO
const log = { const log = {
@@ -70,15 +72,8 @@ export class CanvasIO {
} }
return new Promise((resolve) => { return new Promise((resolve) => {
const tempCanvas = document.createElement('canvas'); const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(this.canvas.width, this.canvas.height);
const maskCanvas = document.createElement('canvas'); const { canvas: maskCanvas, ctx: maskCtx } = createCanvas(this.canvas.width, this.canvas.height);
tempCanvas.width = this.canvas.width;
tempCanvas.height = this.canvas.height;
maskCanvas.width = this.canvas.width;
maskCanvas.height = this.canvas.height;
const tempCtx = tempCanvas.getContext('2d');
const maskCtx = maskCanvas.getContext('2d');
tempCtx.fillStyle = '#ffffff'; tempCtx.fillStyle = '#ffffff';
tempCtx.fillRect(0, 0, this.canvas.width, this.canvas.height); tempCtx.fillRect(0, 0, this.canvas.width, this.canvas.height);
@@ -249,10 +244,7 @@ export class CanvasIO {
try { try {
log.debug("Adding input to canvas:", {inputImage}); log.debug("Adding input to canvas:", {inputImage});
const tempCanvas = document.createElement('canvas'); const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(inputImage.width, inputImage.height);
const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = inputImage.width;
tempCanvas.height = inputImage.height;
const imgData = new ImageData( const imgData = new ImageData(
inputImage.data, inputImage.data,

View File

@@ -1,7 +1,8 @@
import {saveImage, getImage, removeImage} from "./db.js"; import {saveImage, getImage, removeImage} from "./db.js";
import {createModuleLogger} from "./LoggerUtils.js"; import {createModuleLogger} from "./LoggerUtils.js";
import {generateUUID, snapToGrid, getSnapAdjustment, worldToLocal, localToWorld} from "./CommonUtils.js"; import {generateUUID, snapToGrid, getSnapAdjustment, worldToLocal, localToWorld, createCanvas, generateUniqueFileName} from "./CommonUtils.js";
import {withErrorHandling, createValidationError, safeExecute} from "./ErrorHandler.js"; import {withErrorHandling, createValidationError, safeExecute} from "./ErrorHandler.js";
import {createImageFromSource} from "./ImageUtils.js";
// Inicjalizacja loggera dla modułu CanvasLayers // Inicjalizacja loggera dla modułu CanvasLayers
const log = createModuleLogger('CanvasLayers'); const log = createModuleLogger('CanvasLayers');
@@ -557,21 +558,7 @@ export class CanvasLayers {
// Funkcja fallback do zapisu // Funkcja fallback do zapisu
const saveWithFallback = async (fileName) => { const saveWithFallback = async (fileName) => {
try { try {
const getUniqueFileName = (baseName) => { const uniqueFileName = generateUniqueFileName(fileName, this.canvas.node.id);
// Sprawdź czy nazwa już zawiera identyfikator node-a (zapobiega nieskończonej pętli)
const nodePattern = new RegExp(`_node_${this.canvas.node.id}(?:_node_\\d+)*`);
if (nodePattern.test(baseName)) {
// Usuń wszystkie poprzednie identyfikatory node-ów i dodaj tylko jeden
const cleanName = baseName.replace(/_node_\d+/g, '');
const extension = cleanName.split('.').pop();
const nameWithoutExt = cleanName.replace(`.${extension}`, '');
return `${nameWithoutExt}_node_${this.canvas.node.id}.${extension}`;
}
const extension = baseName.split('.').pop();
const nameWithoutExt = baseName.replace(`.${extension}`, '');
return `${nameWithoutExt}_node_${this.canvas.node.id}.${extension}`;
};
const uniqueFileName = getUniqueFileName(fileName);
return await this.canvas.saveToServer(uniqueFileName); return await this.canvas.saveToServer(uniqueFileName);
} catch (error) { } catch (error) {
console.warn(`Failed to save with unique name, falling back to original: ${fileName}`, error); console.warn(`Failed to save with unique name, falling back to original: ${fileName}`, error);

View File

@@ -5,7 +5,8 @@ import {$el} from "../../scripts/ui.js";
import {Canvas} from "./Canvas.js"; import {Canvas} from "./Canvas.js";
import {clearAllCanvasStates} from "./db.js"; import {clearAllCanvasStates} from "./db.js";
import {ImageCache} from "./ImageCache.js"; import {ImageCache} from "./ImageCache.js";
import {validateImageData, convertImageData, applyMaskToImageData, prepareImageForCanvas} from "./ImageUtils.js"; import {validateImageData, convertImageData, applyMaskToImageData, prepareImageForCanvas, createImageFromSource} from "./ImageUtils.js";
import {generateUniqueFileName} from "./CommonUtils.js";
import {logger, LogLevel} from "./logger.js"; import {logger, LogLevel} from "./logger.js";
// Inicjalizacja loggera dla modułu Canvas_view // Inicjalizacja loggera dla modułu Canvas_view
@@ -930,31 +931,10 @@ async function createCanvasWidget(node, widget, app) {
window.canvasExecutionStates = new Map(); window.canvasExecutionStates = new Map();
} }
// Unikalna nazwa pliku dla każdego node-a
const getUniqueFileName = (baseName) => {
// Sprawdź czy nazwa już zawiera identyfikator node-a (zapobiega nieskończonej pętli)
const nodePattern = new RegExp(`_node_${node.id}(?:_node_\\d+)*`);
if (nodePattern.test(baseName)) {
// Usuń wszystkie poprzednie identyfikatory node-ów i dodaj tylko jeden
const cleanName = baseName.replace(/_node_\d+/g, '');
const extension = cleanName.split('.').pop();
const nameWithoutExt = cleanName.replace(`.${extension}`, '');
return `${nameWithoutExt}_node_${node.id}.${extension}`;
}
const extension = baseName.split('.').pop();
const nameWithoutExt = baseName.replace(`.${extension}`, '');
return `${nameWithoutExt}_node_${node.id}.${extension}`;
};
// Funkcja do czyszczenia nazwy pliku z identyfikatorów node-ów
const getCleanFileName = (fileName) => {
return fileName.replace(/_node_\d+/g, '');
};
// Funkcja fallback w przypadku problemów z unikalną nazwą // Funkcja fallback w przypadku problemów z unikalną nazwą
const saveWithFallback = async (fileName) => { const saveWithFallback = async (fileName) => {
try { try {
const uniqueFileName = getUniqueFileName(fileName); const uniqueFileName = generateUniqueFileName(fileName, node.id);
log.debug(`Attempting to save with unique name: ${uniqueFileName}`); log.debug(`Attempting to save with unique name: ${uniqueFileName}`);
return await canvas.saveToServer(uniqueFileName); return await canvas.saveToServer(uniqueFileName);
} catch (error) { } catch (error) {

View File

@@ -174,17 +174,6 @@ export function throttle(func, limit) {
}; };
} }
/**
* Sprawdza czy wartość jest w zakresie
* @param {number} value - Wartość do sprawdzenia
* @param {number} min - Minimalna wartość
* @param {number} max - Maksymalna wartość
* @returns {boolean} Czy wartość jest w zakresie
*/
export function isInRange(value, min, max) {
return value >= min && value <= max;
}
/** /**
* Ogranicza wartość do zakresu * Ogranicza wartość do zakresu
* @param {number} value - Wartość do ograniczenia * @param {number} value - Wartość do ograniczenia
@@ -226,15 +215,47 @@ export function radiansToDegrees(radians) {
} }
/** /**
* Oblicza odległość między dwoma punktami * Tworzy canvas z kontekstem - eliminuje duplikaty w kodzie
* @param {number} x1 - X pierwszego punktu * @param {number} width - Szerokość canvas
* @param {number} y1 - Y pierwszego punktu * @param {number} height - Wysokość canvas
* @param {number} x2 - X drugiego punktu * @param {string} contextType - Typ kontekstu (domyślnie '2d')
* @param {number} y2 - Y drugiego punktu * @param {Object} contextOptions - Opcje kontekstu
* @returns {number} Odległość * @returns {Object} Obiekt z canvas i ctx
*/ */
export function distance(x1, y1, x2, y2) { export function createCanvas(width, height, contextType = '2d', contextOptions = {}) {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); const canvas = document.createElement('canvas');
if (width) canvas.width = width;
if (height) canvas.height = height;
const ctx = canvas.getContext(contextType, contextOptions);
return { canvas, ctx };
}
/**
* Normalizuje wartość do zakresu Uint8 (0-255)
* @param {number} value - Wartość do znormalizowania (0-1)
* @returns {number} Wartość w zakresie 0-255
*/
export function normalizeToUint8(value) {
return Math.max(0, Math.min(255, Math.round(value * 255)));
}
/**
* Generuje unikalną nazwę pliku z identyfikatorem node-a
* @param {string} baseName - Podstawowa nazwa pliku
* @param {string|number} nodeId - Identyfikator node-a
* @returns {string} Unikalna nazwa pliku
*/
export function generateUniqueFileName(baseName, nodeId) {
const nodePattern = new RegExp(`_node_${nodeId}(?:_node_\\d+)*`);
if (nodePattern.test(baseName)) {
const cleanName = baseName.replace(/_node_\d+/g, '');
const extension = cleanName.split('.').pop();
const nameWithoutExt = cleanName.replace(`.${extension}`, '');
return `${nameWithoutExt}_node_${nodeId}.${extension}`;
}
const extension = baseName.split('.').pop();
const nameWithoutExt = baseName.replace(`.${extension}`, '');
return `${nameWithoutExt}_node_${nodeId}.${extension}`;
} }
/** /**

View File

@@ -356,6 +356,20 @@ export function getImageInfo(image) {
}; };
} }
/**
* Tworzy obraz z podanego źródła - eliminuje duplikaty w kodzie
* @param {string} source - Źródło obrazu (URL, data URL, etc.)
* @returns {Promise<HTMLImageElement>} Promise z obrazem
*/
export function createImageFromSource(source) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = source;
});
}
/** /**
* Tworzy pusty obraz o podanych wymiarach * Tworzy pusty obraz o podanych wymiarach
* @param {number} width - Szerokość * @param {number} width - Szerokość

170
js/db.js
View File

@@ -10,6 +10,48 @@ const DB_VERSION = 2; // Zwiększono wersję, aby wymusić aktualizację schemat
let db; let db;
/**
* Funkcja pomocnicza do tworzenia żądań IndexedDB z ujednoliconą obsługą błędów
* @param {IDBObjectStore} store - Store IndexedDB
* @param {string} operation - Nazwa operacji (get, put, delete, clear)
* @param {*} data - Dane dla operacji (opcjonalne)
* @param {string} errorMessage - Wiadomość błędu
* @returns {Promise} Promise z wynikiem operacji
*/
function createDBRequest(store, operation, data, errorMessage) {
return new Promise((resolve, reject) => {
let request;
// Wybierz odpowiednią operację
switch (operation) {
case 'get':
request = store.get(data);
break;
case 'put':
request = store.put(data);
break;
case 'delete':
request = store.delete(data);
break;
case 'clear':
request = store.clear();
break;
default:
reject(new Error(`Unknown operation: ${operation}`));
return;
}
request.onerror = (event) => {
log.error(errorMessage, event.target.error);
reject(errorMessage);
};
request.onsuccess = (event) => {
resolve(event.target.result);
};
});
}
function openDB() { function openDB() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (db) { if (db) {
@@ -49,139 +91,71 @@ function openDB() {
export async function getCanvasState(id) { export async function getCanvasState(id) {
log.info(`Getting state for id: ${id}`); log.info(`Getting state for id: ${id}`);
const db = await openDB(); const db = await openDB();
return new Promise((resolve, reject) => { const transaction = db.transaction([STATE_STORE_NAME], 'readonly');
const transaction = db.transaction([STATE_STORE_NAME], 'readonly'); const store = transaction.objectStore(STATE_STORE_NAME);
const store = transaction.objectStore(STATE_STORE_NAME);
const request = store.get(id);
request.onerror = (event) => { const result = await createDBRequest(store, 'get', id, "Error getting canvas state");
log.error("Error getting canvas state:", event.target.error); log.debug(`Get success for id: ${id}`, result ? 'found' : 'not found');
reject("Error getting state."); return result ? result.state : null;
};
request.onsuccess = (event) => {
log.debug(`Get success for id: ${id}`, event.target.result ? 'found' : 'not found');
resolve(event.target.result ? event.target.result.state : null);
};
});
} }
export async function setCanvasState(id, state) { export async function setCanvasState(id, state) {
log.info(`Setting state for id: ${id}`); log.info(`Setting state for id: ${id}`);
const db = await openDB(); const db = await openDB();
return new Promise((resolve, reject) => { const transaction = db.transaction([STATE_STORE_NAME], 'readwrite');
const transaction = db.transaction([STATE_STORE_NAME], 'readwrite'); const store = transaction.objectStore(STATE_STORE_NAME);
const store = transaction.objectStore(STATE_STORE_NAME);
const request = store.put({id, state});
request.onerror = (event) => { await createDBRequest(store, 'put', {id, state}, "Error setting canvas state");
log.error("Error setting canvas state:", event.target.error); log.debug(`Set success for id: ${id}`);
reject("Error setting state.");
};
request.onsuccess = () => {
log.debug(`Set success for id: ${id}`);
resolve();
};
});
} }
export async function removeCanvasState(id) { export async function removeCanvasState(id) {
log.info(`Removing state for id: ${id}`); log.info(`Removing state for id: ${id}`);
const db = await openDB(); const db = await openDB();
return new Promise((resolve, reject) => { const transaction = db.transaction([STATE_STORE_NAME], 'readwrite');
const transaction = db.transaction([STATE_STORE_NAME], 'readwrite'); const store = transaction.objectStore(STATE_STORE_NAME);
const store = transaction.objectStore(STATE_STORE_NAME);
const request = store.delete(id);
request.onerror = (event) => { await createDBRequest(store, 'delete', id, "Error removing canvas state");
log.error("Error removing canvas state:", event.target.error); log.debug(`Remove success for id: ${id}`);
reject("Error removing state.");
};
request.onsuccess = () => {
log.debug(`Remove success for id: ${id}`);
resolve();
};
});
} }
export async function saveImage(imageId, imageSrc) { export async function saveImage(imageId, imageSrc) {
log.info(`Saving image with id: ${imageId}`); log.info(`Saving image with id: ${imageId}`);
const db = await openDB(); const db = await openDB();
return new Promise((resolve, reject) => { const transaction = db.transaction([IMAGE_STORE_NAME], 'readwrite');
const transaction = db.transaction([IMAGE_STORE_NAME], 'readwrite'); const store = transaction.objectStore(IMAGE_STORE_NAME);
const store = transaction.objectStore(IMAGE_STORE_NAME);
const request = store.put({imageId, imageSrc});
request.onerror = (event) => { await createDBRequest(store, 'put', {imageId, imageSrc}, "Error saving image");
log.error("Error saving image:", event.target.error); log.debug(`Image saved successfully for id: ${imageId}`);
reject("Error saving image.");
};
request.onsuccess = () => {
log.debug(`Image saved successfully for id: ${imageId}`);
resolve();
};
});
} }
export async function getImage(imageId) { export async function getImage(imageId) {
log.info(`Getting image with id: ${imageId}`); log.info(`Getting image with id: ${imageId}`);
const db = await openDB(); const db = await openDB();
return new Promise((resolve, reject) => { const transaction = db.transaction([IMAGE_STORE_NAME], 'readonly');
const transaction = db.transaction([IMAGE_STORE_NAME], 'readonly'); const store = transaction.objectStore(IMAGE_STORE_NAME);
const store = transaction.objectStore(IMAGE_STORE_NAME);
const request = store.get(imageId);
request.onerror = (event) => { const result = await createDBRequest(store, 'get', imageId, "Error getting image");
log.error("Error getting image:", event.target.error); log.debug(`Get image success for id: ${imageId}`, result ? 'found' : 'not found');
reject("Error getting image."); return result ? result.imageSrc : null;
};
request.onsuccess = (event) => {
log.debug(`Get image success for id: ${imageId}`, event.target.result ? 'found' : 'not found');
resolve(event.target.result ? event.target.result.imageSrc : null);
};
});
} }
export async function removeImage(imageId) { export async function removeImage(imageId) {
log.info(`Removing image with id: ${imageId}`); log.info(`Removing image with id: ${imageId}`);
const db = await openDB(); const db = await openDB();
return new Promise((resolve, reject) => { const transaction = db.transaction([IMAGE_STORE_NAME], 'readwrite');
const transaction = db.transaction([IMAGE_STORE_NAME], 'readwrite'); const store = transaction.objectStore(IMAGE_STORE_NAME);
const store = transaction.objectStore(IMAGE_STORE_NAME);
const request = store.delete(imageId);
request.onerror = (event) => { await createDBRequest(store, 'delete', imageId, "Error removing image");
log.error("Error removing image:", event.target.error); log.debug(`Remove image success for id: ${imageId}`);
reject("Error removing image.");
};
request.onsuccess = () => {
log.debug(`Remove image success for id: ${imageId}`);
resolve();
};
});
} }
export async function clearAllCanvasStates() { export async function clearAllCanvasStates() {
log.info("Clearing all canvas states..."); log.info("Clearing all canvas states...");
const db = await openDB(); const db = await openDB();
return new Promise((resolve, reject) => { const transaction = db.transaction([STATE_STORE_NAME], 'readwrite');
const transaction = db.transaction([STATE_STORE_NAME], 'readwrite'); const store = transaction.objectStore(STATE_STORE_NAME);
const store = transaction.objectStore(STATE_STORE_NAME);
const request = store.clear();
request.onerror = (event) => { await createDBRequest(store, 'clear', null, "Error clearing canvas states");
log.error("Error clearing canvas states:", event.target.error); log.info("All canvas states cleared successfully.");
reject("Error clearing states.");
};
request.onsuccess = () => {
log.info("All canvas states cleared successfully.");
resolve();
};
});
} }