suisuyy
Rename project back to xpaintai in package.json and metadata.json; add QuestionMarkIcon component; enhance AI features with ask functionality in useAiFeatures hook and App component
02ce812
| import React, { useCallback } from 'react'; | |
| import { getBlankCanvasDataURL } from '../utils/canvasUtils'; | |
| import { CanvasHistoryHook } from './useCanvasHistory'; | |
| import { ConfirmModalHook } from './useConfirmModal'; | |
| import { ToastMessage } from './useToasts'; | |
| interface UseCanvasFileUtilsProps { | |
| canvasState: { | |
| currentDataURL: string | null; | |
| canvasWidth: number; | |
| canvasHeight: number; | |
| }; | |
| historyActions: { | |
| updateCanvasState: CanvasHistoryHook['updateCanvasState']; | |
| }; | |
| uiActions: { | |
| showToast: (message: string, type: ToastMessage['type']) => void; | |
| setZoomLevel: (zoom: number) => void; | |
| }; | |
| confirmModalActions: { | |
| requestConfirmation: ConfirmModalHook['requestConfirmation']; | |
| }; | |
| } | |
| export interface CanvasFileUtilsHook { | |
| handleLoadImageFile: (event: React.ChangeEvent<HTMLInputElement>) => void; | |
| handleExportImage: () => void; | |
| handleClearCanvas: () => void; | |
| handleCanvasSizeChange: (newWidth: number, newHeight: number) => void; | |
| } | |
| export const useCanvasFileUtils = ({ | |
| canvasState, | |
| historyActions, | |
| uiActions, | |
| confirmModalActions, | |
| }: UseCanvasFileUtilsProps): CanvasFileUtilsHook => { | |
| const { currentDataURL, canvasWidth, canvasHeight } = canvasState; | |
| const { updateCanvasState } = historyActions; | |
| const { showToast, setZoomLevel } = uiActions; | |
| const { requestConfirmation } = confirmModalActions; | |
| const handleLoadImageFile = useCallback((event: React.ChangeEvent<HTMLInputElement>) => { | |
| const file = event.target.files?.[0]; | |
| if (file) { | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| const imageDataUrl = e.target?.result as string; | |
| if (imageDataUrl) { | |
| const img = new Image(); | |
| img.onload = () => { | |
| let imageToDrawWidth = img.naturalWidth; | |
| let imageToDrawHeight = img.naturalHeight; | |
| if (img.naturalWidth > canvasWidth || img.naturalHeight > canvasHeight) { | |
| const widthRatio = canvasWidth / img.naturalWidth; | |
| const heightRatio = canvasHeight / img.naturalHeight; | |
| const scaleFactor = Math.min(widthRatio, heightRatio); | |
| imageToDrawWidth = Math.max(1, Math.floor(img.naturalWidth * scaleFactor)); | |
| imageToDrawHeight = Math.max(1, Math.floor(img.naturalHeight * scaleFactor)); | |
| } | |
| const finalCanvasWidth = canvasWidth; | |
| const finalCanvasHeight = canvasHeight; | |
| const tempCanvas = document.createElement('canvas'); | |
| tempCanvas.width = finalCanvasWidth; | |
| tempCanvas.height = finalCanvasHeight; | |
| const tempCtx = tempCanvas.getContext('2d'); | |
| if (tempCtx) { | |
| tempCtx.fillStyle = '#FFFFFF'; | |
| tempCtx.fillRect(0, 0, finalCanvasWidth, finalCanvasHeight); | |
| // Change: Draw image at top-left (0,0) | |
| const drawX = 0; | |
| const drawY = 0; | |
| tempCtx.drawImage(img, drawX, drawY, imageToDrawWidth, imageToDrawHeight); | |
| const compositeDataURL = tempCanvas.toDataURL('image/png'); | |
| updateCanvasState(compositeDataURL, finalCanvasWidth, finalCanvasHeight); | |
| showToast('Image loaded successfully.', 'success'); | |
| } | |
| }; | |
| img.onerror = () => showToast("Error loading image file for processing.", 'error'); | |
| img.src = imageDataUrl; | |
| } | |
| }; | |
| reader.onerror = () => showToast("Error reading image file.", 'error'); | |
| reader.readAsDataURL(file); | |
| if(event.target) event.target.value = ''; | |
| } | |
| }, [canvasWidth, canvasHeight, updateCanvasState, showToast]); | |
| const handleExportImage = useCallback(() => { | |
| if (currentDataURL) { | |
| const link = document.createElement('a'); | |
| link.href = currentDataURL; | |
| link.download = `paint-masterpiece-${Date.now()}.png`; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| showToast('Image exported!', 'success'); | |
| } else { | |
| showToast('No image to export.', 'info'); | |
| } | |
| }, [currentDataURL, showToast]); | |
| const handleClearCanvas = useCallback(() => { | |
| const blankCanvas = getBlankCanvasDataURL(canvasWidth, canvasHeight); | |
| updateCanvasState(blankCanvas, canvasWidth, canvasHeight); | |
| showToast("Canvas cleared.", "info"); | |
| }, [canvasWidth, canvasHeight, updateCanvasState, showToast]); | |
| const handleCanvasSizeChange = useCallback((newWidth: number, newHeight: number) => { | |
| if (newWidth === canvasWidth && newHeight === canvasHeight) { | |
| return; | |
| } | |
| requestConfirmation( | |
| "Confirm Canvas Size Change", | |
| "Changing canvas size will clear the current drawing and history. Are you sure you want to continue?", | |
| () => { | |
| const blankCanvas = getBlankCanvasDataURL(newWidth, newHeight); | |
| updateCanvasState(blankCanvas, newWidth, newHeight); | |
| setZoomLevel(0.5); | |
| showToast(`Canvas resized to ${newWidth}x${newHeight}px. Drawing cleared.`, 'info'); | |
| }, | |
| { isDestructive: true } | |
| ); | |
| }, [canvasWidth, canvasHeight, requestConfirmation, updateCanvasState, setZoomLevel, showToast]); | |
| return { handleLoadImageFile, handleExportImage, handleClearCanvas, handleCanvasSizeChange }; | |
| }; | |