@@ -8,29 +8,118 @@ interface Props {
88 language ?: string ;
99}
1010
11+ /**
12+ * Compress an image file using the Canvas API.
13+ * Resizes the image to fit within maxWidth x maxHeight while maintaining aspect ratio.
14+ * PNG images are converted to JPEG for better compression.
15+ *
16+ * @param file - The image file to compress
17+ * @param maxWidth - Maximum width in pixels (default: 1920)
18+ * @param maxHeight - Maximum height in pixels (default: 1920)
19+ * @param quality - JPEG compression quality from 0 to 1 (default: 0.85)
20+ * @returns Promise that resolves to a base64 data URL of the compressed image
21+ * @throws Error if image loading or canvas operations fail
22+ */
23+ async function compressImage ( file : File , maxWidth : number = 1920 , maxHeight : number = 1920 , quality : number = 0.85 ) : Promise < string > {
24+ return new Promise ( ( resolve , reject ) => {
25+ const reader = new FileReader ( ) ;
26+
27+ reader . onload = ( e ) => {
28+ const img = new Image ( ) ;
29+
30+ img . onload = ( ) => {
31+ const canvas = document . createElement ( 'canvas' ) ;
32+ let width = img . width ;
33+ let height = img . height ;
34+
35+ // Calculate new dimensions while maintaining aspect ratio
36+ if ( width > maxWidth || height > maxHeight ) {
37+ const aspectRatio = width / height ;
38+ if ( width > height ) {
39+ width = maxWidth ;
40+ height = maxWidth / aspectRatio ;
41+ } else {
42+ height = maxHeight ;
43+ width = maxHeight * aspectRatio ;
44+ }
45+ }
46+
47+ canvas . width = width ;
48+ canvas . height = height ;
49+
50+ const ctx = canvas . getContext ( '2d' ) ;
51+ if ( ! ctx ) {
52+ reject ( new Error ( 'Failed to get canvas context' ) ) ;
53+ return ;
54+ }
55+
56+ ctx . drawImage ( img , 0 , 0 , width , height ) ;
57+
58+ // Convert all raster images to JPEG for consistent compression
59+ // JPEG provides good quality at significantly smaller file sizes
60+ const dataUrl = canvas . toDataURL ( 'image/jpeg' , quality ) ;
61+
62+ resolve ( dataUrl ) ;
63+ } ;
64+
65+ img . onerror = ( ) => {
66+ reject ( new Error ( 'Failed to load image' ) ) ;
67+ } ;
68+
69+ img . src = e . target ?. result as string ;
70+ } ;
71+
72+ reader . onerror = ( ) => {
73+ reject ( new Error ( 'Failed to read file' ) ) ;
74+ } ;
75+
76+ reader . readAsDataURL ( file ) ;
77+ } ) ;
78+ }
79+
1180export function EditorDrawerImageContent ( { localNode, handleFieldChange, language = "en" } : Props ) {
1281 const t = getTranslations ( language ) ;
1382
14- // Convert file to base64 and update node data
83+ // Convert file to base64 with compression
1584 const handleFileChange = async ( e : React . ChangeEvent < HTMLInputElement > ) => {
1685 const file = e . target . files ?. [ 0 ] ;
1786 if ( ! file ) return ;
18- const reader = new FileReader ( ) ;
19- reader . onload = ( ) => {
20- if ( typeof reader . result === "string" ) {
21- handleFieldChange ( "data" , reader . result ) ;
87+
88+ try {
89+ // For SVG files, don't compress (they're already vector graphics)
90+ if ( file . type === 'image/svg+xml' ) {
91+ const reader = new FileReader ( ) ;
92+ reader . onload = ( ) => {
93+ if ( typeof reader . result === "string" ) {
94+ handleFieldChange ( "data" , reader . result ) ;
95+ }
96+ } ;
97+ reader . readAsDataURL ( file ) ;
98+ } else {
99+ // Compress JPEG, PNG, and WebP images
100+ const compressedDataUrl = await compressImage ( file ) ;
101+ handleFieldChange ( "data" , compressedDataUrl ) ;
22102 }
23- } ;
24- reader . readAsDataURL ( file ) ;
103+ } catch ( error ) {
104+ console . error ( "Failed to process image:" , error ) ;
105+ // Fallback to original file if compression fails
106+ const reader = new FileReader ( ) ;
107+ reader . onload = ( ) => {
108+ if ( typeof reader . result === "string" ) {
109+ handleFieldChange ( "data" , reader . result ) ;
110+ }
111+ } ;
112+ reader . readAsDataURL ( file ) ;
113+ }
25114 } ;
26115
27116 return (
28117 < div className = "panel-content" >
29118 < div className = "form-group" >
30- < label > { t . image } (JPG, PNG, SVG)</ label >
119+ < label > { t . image } (JPG, PNG, WebP, SVG)</ label >
31120 < input
32121 type = "file"
33- accept = "image/png,image/jpeg,image/svg+xml"
122+ accept = "image/png,image/jpeg,image/webp,image/ svg+xml"
34123 onChange = { handleFileChange }
35124 />
36125 </ div >
0 commit comments