diff --git a/js/CanvasIO.js b/js/CanvasIO.js index bf55451..2c0182a 100644 --- a/js/CanvasIO.js +++ b/js/CanvasIO.js @@ -1,5 +1,7 @@ import {saveImage, getImage, removeImage} from "./db.js"; import {logger, LogLevel} from "./logger.js"; +import {createCanvas, normalizeToUint8} from "./CommonUtils.js"; +import {createImageFromSource} from "./ImageUtils.js"; // Inicjalizacja loggera dla modułu CanvasIO const log = { @@ -70,15 +72,8 @@ export class CanvasIO { } return new Promise((resolve) => { - const tempCanvas = document.createElement('canvas'); - const maskCanvas = document.createElement('canvas'); - 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'); + const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(this.canvas.width, this.canvas.height); + const { canvas: maskCanvas, ctx: maskCtx } = createCanvas(this.canvas.width, this.canvas.height); tempCtx.fillStyle = '#ffffff'; tempCtx.fillRect(0, 0, this.canvas.width, this.canvas.height); @@ -249,10 +244,7 @@ export class CanvasIO { try { log.debug("Adding input to canvas:", {inputImage}); - const tempCanvas = document.createElement('canvas'); - const tempCtx = tempCanvas.getContext('2d'); - tempCanvas.width = inputImage.width; - tempCanvas.height = inputImage.height; + const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(inputImage.width, inputImage.height); const imgData = new ImageData( inputImage.data, diff --git a/js/CanvasLayers.js b/js/CanvasLayers.js index e33031e..8500d15 100644 --- a/js/CanvasLayers.js +++ b/js/CanvasLayers.js @@ -1,7 +1,8 @@ import {saveImage, getImage, removeImage} from "./db.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 {createImageFromSource} from "./ImageUtils.js"; // Inicjalizacja loggera dla modułu CanvasLayers const log = createModuleLogger('CanvasLayers'); @@ -557,21 +558,7 @@ export class CanvasLayers { // Funkcja fallback do zapisu const saveWithFallback = async (fileName) => { try { - const getUniqueFileName = (baseName) => { - // 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); + const uniqueFileName = generateUniqueFileName(fileName, this.canvas.node.id); return await this.canvas.saveToServer(uniqueFileName); } catch (error) { console.warn(`Failed to save with unique name, falling back to original: ${fileName}`, error); diff --git a/js/Canvas_view.js b/js/Canvas_view.js index e2c504b..4a2810c 100644 --- a/js/Canvas_view.js +++ b/js/Canvas_view.js @@ -5,7 +5,8 @@ import {$el} from "../../scripts/ui.js"; import {Canvas} from "./Canvas.js"; import {clearAllCanvasStates} from "./db.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"; // Inicjalizacja loggera dla modułu Canvas_view @@ -930,31 +931,10 @@ async function createCanvasWidget(node, widget, app) { 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ą const saveWithFallback = async (fileName) => { try { - const uniqueFileName = getUniqueFileName(fileName); + const uniqueFileName = generateUniqueFileName(fileName, node.id); log.debug(`Attempting to save with unique name: ${uniqueFileName}`); return await canvas.saveToServer(uniqueFileName); } catch (error) { diff --git a/js/CommonUtils.js b/js/CommonUtils.js index dd0cfc9..6fa7964 100644 --- a/js/CommonUtils.js +++ b/js/CommonUtils.js @@ -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 * @param {number} value - Wartość do ograniczenia @@ -226,15 +215,47 @@ export function radiansToDegrees(radians) { } /** - * Oblicza odległość między dwoma punktami - * @param {number} x1 - X pierwszego punktu - * @param {number} y1 - Y pierwszego punktu - * @param {number} x2 - X drugiego punktu - * @param {number} y2 - Y drugiego punktu - * @returns {number} Odległość + * Tworzy canvas z kontekstem - eliminuje duplikaty w kodzie + * @param {number} width - Szerokość canvas + * @param {number} height - Wysokość canvas + * @param {string} contextType - Typ kontekstu (domyślnie '2d') + * @param {Object} contextOptions - Opcje kontekstu + * @returns {Object} Obiekt z canvas i ctx */ -export function distance(x1, y1, x2, y2) { - return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); +export function createCanvas(width, height, contextType = '2d', contextOptions = {}) { + 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}`; } /** diff --git a/js/ImageUtils.js b/js/ImageUtils.js index d08536b..e8fb43c 100644 --- a/js/ImageUtils.js +++ b/js/ImageUtils.js @@ -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} 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 * @param {number} width - Szerokość diff --git a/js/db.js b/js/db.js index 2a3934f..6e9e117 100644 --- a/js/db.js +++ b/js/db.js @@ -10,6 +10,48 @@ const DB_VERSION = 2; // Zwiększono wersję, aby wymusić aktualizację schemat 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() { return new Promise((resolve, reject) => { if (db) { @@ -49,139 +91,71 @@ function openDB() { export async function getCanvasState(id) { log.info(`Getting state for id: ${id}`); const db = await openDB(); - return new Promise((resolve, reject) => { - const transaction = db.transaction([STATE_STORE_NAME], 'readonly'); - const store = transaction.objectStore(STATE_STORE_NAME); - const request = store.get(id); - - request.onerror = (event) => { - log.error("Error getting canvas state:", event.target.error); - reject("Error getting state."); - }; - - 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); - }; - }); + const transaction = db.transaction([STATE_STORE_NAME], 'readonly'); + const store = transaction.objectStore(STATE_STORE_NAME); + + const result = await createDBRequest(store, 'get', id, "Error getting canvas state"); + log.debug(`Get success for id: ${id}`, result ? 'found' : 'not found'); + return result ? result.state : null; } export async function setCanvasState(id, state) { log.info(`Setting state for id: ${id}`); const db = await openDB(); - return new Promise((resolve, reject) => { - const transaction = db.transaction([STATE_STORE_NAME], 'readwrite'); - const store = transaction.objectStore(STATE_STORE_NAME); - const request = store.put({id, state}); - - request.onerror = (event) => { - log.error("Error setting canvas state:", event.target.error); - reject("Error setting state."); - }; - - request.onsuccess = () => { - log.debug(`Set success for id: ${id}`); - resolve(); - }; - }); + const transaction = db.transaction([STATE_STORE_NAME], 'readwrite'); + const store = transaction.objectStore(STATE_STORE_NAME); + + await createDBRequest(store, 'put', {id, state}, "Error setting canvas state"); + log.debug(`Set success for id: ${id}`); } export async function removeCanvasState(id) { log.info(`Removing state for id: ${id}`); const db = await openDB(); - return new Promise((resolve, reject) => { - const transaction = db.transaction([STATE_STORE_NAME], 'readwrite'); - const store = transaction.objectStore(STATE_STORE_NAME); - const request = store.delete(id); - - request.onerror = (event) => { - log.error("Error removing canvas state:", event.target.error); - reject("Error removing state."); - }; - - request.onsuccess = () => { - log.debug(`Remove success for id: ${id}`); - resolve(); - }; - }); + const transaction = db.transaction([STATE_STORE_NAME], 'readwrite'); + const store = transaction.objectStore(STATE_STORE_NAME); + + await createDBRequest(store, 'delete', id, "Error removing canvas state"); + log.debug(`Remove success for id: ${id}`); } export async function saveImage(imageId, imageSrc) { log.info(`Saving image with id: ${imageId}`); const db = await openDB(); - return new Promise((resolve, reject) => { - const transaction = db.transaction([IMAGE_STORE_NAME], 'readwrite'); - const store = transaction.objectStore(IMAGE_STORE_NAME); - const request = store.put({imageId, imageSrc}); - - request.onerror = (event) => { - log.error("Error saving image:", event.target.error); - reject("Error saving image."); - }; - - request.onsuccess = () => { - log.debug(`Image saved successfully for id: ${imageId}`); - resolve(); - }; - }); + const transaction = db.transaction([IMAGE_STORE_NAME], 'readwrite'); + const store = transaction.objectStore(IMAGE_STORE_NAME); + + await createDBRequest(store, 'put', {imageId, imageSrc}, "Error saving image"); + log.debug(`Image saved successfully for id: ${imageId}`); } export async function getImage(imageId) { log.info(`Getting image with id: ${imageId}`); const db = await openDB(); - return new Promise((resolve, reject) => { - const transaction = db.transaction([IMAGE_STORE_NAME], 'readonly'); - const store = transaction.objectStore(IMAGE_STORE_NAME); - const request = store.get(imageId); - - request.onerror = (event) => { - log.error("Error getting image:", event.target.error); - reject("Error getting image."); - }; - - 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); - }; - }); + const transaction = db.transaction([IMAGE_STORE_NAME], 'readonly'); + const store = transaction.objectStore(IMAGE_STORE_NAME); + + const result = await createDBRequest(store, 'get', imageId, "Error getting image"); + log.debug(`Get image success for id: ${imageId}`, result ? 'found' : 'not found'); + return result ? result.imageSrc : null; } export async function removeImage(imageId) { log.info(`Removing image with id: ${imageId}`); const db = await openDB(); - return new Promise((resolve, reject) => { - const transaction = db.transaction([IMAGE_STORE_NAME], 'readwrite'); - const store = transaction.objectStore(IMAGE_STORE_NAME); - const request = store.delete(imageId); - - request.onerror = (event) => { - log.error("Error removing image:", event.target.error); - reject("Error removing image."); - }; - - request.onsuccess = () => { - log.debug(`Remove image success for id: ${imageId}`); - resolve(); - }; - }); + const transaction = db.transaction([IMAGE_STORE_NAME], 'readwrite'); + const store = transaction.objectStore(IMAGE_STORE_NAME); + + await createDBRequest(store, 'delete', imageId, "Error removing image"); + log.debug(`Remove image success for id: ${imageId}`); } export async function clearAllCanvasStates() { log.info("Clearing all canvas states..."); const db = await openDB(); - return new Promise((resolve, reject) => { - const transaction = db.transaction([STATE_STORE_NAME], 'readwrite'); - const store = transaction.objectStore(STATE_STORE_NAME); - const request = store.clear(); - - request.onerror = (event) => { - log.error("Error clearing canvas states:", event.target.error); - reject("Error clearing states."); - }; - - request.onsuccess = () => { - log.info("All canvas states cleared successfully."); - resolve(); - }; - }); + const transaction = db.transaction([STATE_STORE_NAME], 'readwrite'); + const store = transaction.objectStore(STATE_STORE_NAME); + + await createDBRequest(store, 'clear', null, "Error clearing canvas states"); + log.info("All canvas states cleared successfully."); }