@@ -3,7 +3,7 @@ import { toast } from 'react-hot-toast'
3
3
import { getFromStorage , setToStorage } from '../common/storage'
4
4
import type { Bookmark } from '../layouts/search/bookmarks/types/bookmark.types'
5
5
6
- const MAX_BOOKMARK_SIZE = 1024 * 1024
6
+ const MAX_BOOKMARK_SIZE = 1.5 * 1024 * 1024
7
7
8
8
export interface BookmarkStoreContext {
9
9
bookmarks : Bookmark [ ]
@@ -61,50 +61,103 @@ export const BookmarkProvider: React.FC<{ children: React.ReactNode }> = ({
61
61
62
62
const getBookmarkDataSize = ( bookmark : Bookmark ) : number => {
63
63
try {
64
+ if ( bookmark . customImage ) {
65
+ const base64Data = bookmark . customImage . split ( ',' ) [ 1 ] || bookmark . customImage
66
+
67
+ const imageSize = Math . ceil ( ( base64Data . length * 3 ) / 4 )
68
+
69
+ const bookmarkWithoutImage = { ...bookmark }
70
+ bookmarkWithoutImage . customImage = undefined
71
+ const jsonSize = new Blob ( [ JSON . stringify ( bookmarkWithoutImage ) ] ) . size
72
+
73
+ return imageSize + jsonSize
74
+ }
75
+
64
76
const json = JSON . stringify ( bookmark )
65
77
return new Blob ( [ json ] ) . size
66
78
} catch ( e ) {
79
+ console . error ( 'Error calculating bookmark size:' , e )
67
80
return Number . POSITIVE_INFINITY
68
81
}
69
82
}
70
83
71
- const compressImageData = ( imageData : string ) : string => {
84
+ const compressImageData = async ( imageData : string ) : Promise < string > => {
72
85
if ( ! imageData . startsWith ( 'data:image' ) ) {
73
86
return imageData
74
87
}
75
88
76
- const base64 = imageData . split ( ',' ) [ 1 ]
77
- const binaryString = window . atob ( base64 )
78
- const length = binaryString . length
79
-
80
- if ( length > 2 * 1024 * 1024 ) {
81
- throw new Error ( 'Image is too large to process' )
89
+ if ( imageData . startsWith ( 'data:image/gif' ) && imageData . length < MAX_BOOKMARK_SIZE ) {
90
+ return imageData
82
91
}
83
92
84
- const img = new Image ( )
85
- const canvas = document . createElement ( 'canvas' )
86
- const ctx = canvas . getContext ( '2d' )
87
- const maxDimension = 48
93
+ try {
94
+ const base64 = imageData . split ( ',' ) [ 1 ]
95
+ const binaryString = window . atob ( base64 )
96
+ const length = binaryString . length
88
97
89
- canvas . width = maxDimension
90
- canvas . height = maxDimension
91
- img . src = imageData
98
+ if ( length > 3 * 1024 * 1024 ) {
99
+ throw new Error ( 'Image is too large to process' )
100
+ }
92
101
93
- try {
94
- ctx ?. drawImage ( img , 0 , 0 , maxDimension , maxDimension )
95
- return canvas . toDataURL ( 'image/webp' , 0.6 )
102
+ return new Promise ( ( resolve ) => {
103
+ const img = new Image ( )
104
+ img . onload = ( ) => {
105
+ const canvas = document . createElement ( 'canvas' )
106
+ const ctx = canvas . getContext ( '2d' )
107
+ const maxDimension = 128
108
+
109
+ let width = img . width
110
+ let height = img . height
111
+
112
+ if ( width > height ) {
113
+ if ( width > maxDimension ) {
114
+ height = Math . round ( height * ( maxDimension / width ) )
115
+ width = maxDimension
116
+ }
117
+ } else {
118
+ if ( height > maxDimension ) {
119
+ width = Math . round ( width * ( maxDimension / height ) )
120
+ height = maxDimension
121
+ }
122
+ }
123
+
124
+ canvas . width = width
125
+ canvas . height = height
126
+
127
+ ctx ?. drawImage ( img , 0 , 0 , width , height )
128
+
129
+ const format = imageData . startsWith ( 'data:image/gif' )
130
+ ? 'image/png'
131
+ : 'image/webp'
132
+ const quality = 0.7
133
+
134
+ const compressed = canvas . toDataURL ( format , quality )
135
+ resolve ( compressed )
136
+ }
137
+
138
+ img . onerror = ( ) => {
139
+ console . warn ( 'Image compression failed, using original' )
140
+ resolve ( imageData )
141
+ }
142
+
143
+ img . src = imageData
144
+ } )
96
145
} catch ( e ) {
97
- return imageData . substring ( 0 , 50000 )
146
+ console . error ( 'Error in image compression:' , e )
147
+ return imageData
98
148
}
99
149
}
100
150
101
- const prepareBookmarkForStorage = ( bookmark : Bookmark ) : Bookmark => {
151
+ const prepareBookmarkForStorage = async ( bookmark : Bookmark ) : Promise < Bookmark > => {
102
152
const processedBookmark = { ...bookmark , isLocal : true }
103
153
104
154
if ( processedBookmark . customImage && processedBookmark . customImage . length > 50000 ) {
105
155
try {
106
- processedBookmark . customImage = compressImageData ( processedBookmark . customImage )
156
+ processedBookmark . customImage = await compressImageData (
157
+ processedBookmark . customImage ,
158
+ )
107
159
} catch ( err ) {
160
+ console . error ( 'Image processing error:' , err )
108
161
toast . error ( 'خطا در پردازش تصویر. از تصویر پیشفرض استفاده میشود.' )
109
162
110
163
if ( processedBookmark . type === 'BOOKMARK' ) {
@@ -119,12 +172,18 @@ export const BookmarkProvider: React.FC<{ children: React.ReactNode }> = ({
119
172
const addBookmark = async ( bookmark : Bookmark ) => {
120
173
try {
121
174
const bookmarkSize = getBookmarkDataSize ( bookmark )
122
- if ( bookmarkSize > MAX_BOOKMARK_SIZE ) {
123
- toast . error ( 'تصویر انتخاب شده خیلی بزرگ است. لطفاً تصویر کوچکتری انتخاب کنید.' )
175
+
176
+ const isGif = bookmark . customImage ?. startsWith ( 'data:image/gif' )
177
+ const sizeLimit = isGif ? 1.5 * MAX_BOOKMARK_SIZE : MAX_BOOKMARK_SIZE
178
+
179
+ if ( bookmarkSize > sizeLimit ) {
180
+ toast . error (
181
+ `تصویر انتخاب شده (${ ( bookmarkSize / 1024 ) . toFixed ( 1 ) } کیلوبایت) بزرگتر از حداکثر مجاز است.` ,
182
+ )
124
183
return
125
184
}
126
185
127
- const newBookmark = prepareBookmarkForStorage ( bookmark )
186
+ const newBookmark = await prepareBookmarkForStorage ( bookmark )
128
187
const updatedBookmarks = [ ...bookmarks , newBookmark ]
129
188
130
189
try {
@@ -144,6 +203,7 @@ export const BookmarkProvider: React.FC<{ children: React.ReactNode }> = ({
144
203
const localBookmarks = updatedBookmarks . filter ( ( b ) => b . isLocal )
145
204
await setToStorage ( 'bookmarks' , localBookmarks )
146
205
} catch ( error ) {
206
+ console . error ( 'Error adding bookmark:' , error )
147
207
toast . error ( 'خطا در افزودن بوکمارک' )
148
208
}
149
209
}
0 commit comments