@@ -13,6 +13,7 @@ import { innerError } from "WoltLabSuite/Core/Dom/Util";
1313import { getPhrase } from "WoltLabSuite/Core/Language" ;
1414import { createSHA256 } from "hash-wasm" ;
1515import { cropImage , CropperConfiguration } from "WoltLabSuite/Core/Component/Image/Cropper" ;
16+ import { Exif , getExifBytesFromJpeg , getExifBytesFromWebP } from "WoltLabSuite/Core/Image/ExifUtil" ;
1617
1718export type CkeditorDropEvent = {
1819 file : File ;
@@ -44,6 +45,7 @@ async function upload(
4445 element : WoltlabCoreFileUploadElement ,
4546 file : File ,
4647 fileHash : string ,
48+ exifData : Exif | null ,
4749) : Promise < ResponseCompleted | undefined > {
4850 const objectType = element . dataset . objectType ! ;
4951
@@ -54,7 +56,14 @@ async function upload(
5456 const event = new CustomEvent < WoltlabCoreFileElement > ( "uploadStart" , { detail : fileElement } ) ;
5557 element . dispatchEvent ( event ) ;
5658
57- const response = await filesUpload ( file . name , file . size , fileHash , objectType , element . dataset . context || "" ) ;
59+ const response = await filesUpload (
60+ file . name ,
61+ file . size ,
62+ fileHash ,
63+ objectType ,
64+ element . dataset . context || "" ,
65+ exifData ,
66+ ) ;
5867 if ( ! response . ok ) {
5968 const validationError = response . error . getValidationError ( ) ;
6069 if ( validationError === undefined ) {
@@ -118,8 +127,9 @@ async function chunkUploadCompleted(fileElement: WoltlabCoreFileElement, result:
118127 fileElement . uploadCompleted ( result . fileID , result . mimeType , result . link , result . data , result . generateThumbnails ) ;
119128
120129 if ( result . generateThumbnails ) {
121- const response = await generateThumbnails ( result . fileID ) ;
122- fileElement . setThumbnails ( response . unwrap ( ) ) ;
130+ const { filename, fileSize, mimeType, thumbnails } = ( await generateThumbnails ( result . fileID ) ) . unwrap ( ) ;
131+ fileElement . setThumbnails ( thumbnails ) ;
132+ fileElement . updateFileData ( filename , fileSize , mimeType ) ;
123133 }
124134}
125135
@@ -163,7 +173,7 @@ async function resizeImage(element: WoltlabCoreFileUploadElement, file: File): P
163173 const resizeConfiguration = JSON . parse ( element . dataset . resizeConfiguration ! ) as ResizeConfiguration ;
164174
165175 const resizer = new ImageResizer ( ) ;
166- const { image, exif } = await resizer . loadFile ( file ) ;
176+ const { image } = await resizer . loadFile ( file ) ;
167177
168178 const maxHeight = resizeConfiguration . maxHeight === - 1 ? image . height : resizeConfiguration . maxHeight ;
169179 let maxWidth = resizeConfiguration . maxWidth === - 1 ? image . width : resizeConfiguration . maxWidth ;
@@ -187,14 +197,13 @@ async function resizeImage(element: WoltlabCoreFileUploadElement, file: File): P
187197
188198 let fileType : string = resizeConfiguration . fileType ;
189199 if ( fileType === "image/jpeg" || fileType === "image/webp" ) {
190- fileType = "image/jpeg " ;
200+ fileType = "image/webp " ;
191201 } else {
192202 fileType = file . type ;
193203 }
194204
195205 const resizedFile = await resizer . saveFile (
196206 {
197- exif,
198207 image : canvas ,
199208 } ,
200209 file . name ,
@@ -290,6 +299,29 @@ function reportError(element: WoltlabCoreFileUploadElement, file: File | null, m
290299 innerError ( element , message ) ;
291300}
292301
302+ async function getExifBytes ( file : File ) : Promise < Exif | null > {
303+ if ( file . type === "image/jpeg" ) {
304+ try {
305+ const bytes = await getExifBytesFromJpeg ( file ) ;
306+
307+ // ExifUtil returns the entire section but we only need the app data.
308+ // Removing the first 10 bytes drops the 0xFF 0xE1 marker followed by two
309+ // bytes for the length and then 6 bytes for the "Exif\x00\x00" header.
310+ return bytes . slice ( 10 ) ;
311+ } catch {
312+ return null ;
313+ }
314+ } else if ( file . type === "image/webp" ) {
315+ try {
316+ return await getExifBytesFromWebP ( file ) ;
317+ } catch {
318+ return null ;
319+ }
320+ }
321+
322+ return null ;
323+ }
324+
293325export function setup ( ) : void {
294326 wheneverFirstSeen ( "woltlab-core-file-upload" , ( element : WoltlabCoreFileUploadElement ) => {
295327 element . addEventListener ( "upload:files" , ( event : CustomEvent < { files : File [ ] } > ) => {
@@ -311,11 +343,15 @@ export function setup(): void {
311343
312344 element . markAsBusy ( ) ;
313345
346+ const exifData = new Map < File , Exif | null > ( ) ;
347+
314348 let processImage : ( file : File ) => Promise < File > ;
315349 if ( element . dataset . cropperConfiguration ) {
316350 const cropperConfiguration = JSON . parse ( element . dataset . cropperConfiguration ) as CropperConfiguration ;
317351
318352 processImage = async ( file ) => {
353+ exifData . set ( file , await getExifBytes ( file ) ) ;
354+
319355 try {
320356 return await cropImage ( element , file , cropperConfiguration ) ;
321357 } catch ( e ) {
@@ -325,7 +361,11 @@ export function setup(): void {
325361 }
326362 } ;
327363 } else {
328- processImage = async ( file ) => resizeImage ( element , file ) ;
364+ processImage = async ( file ) => {
365+ exifData . set ( file , await getExifBytes ( file ) ) ;
366+
367+ return resizeImage ( element , file ) ;
368+ } ;
329369 }
330370
331371 // Resize all files in parallel but keep the original order. This ensures
@@ -359,7 +399,8 @@ export function setup(): void {
359399 const result = checksums [ i ] ;
360400
361401 if ( result . status === "fulfilled" ) {
362- void upload ( element , validFiles [ i ] , result . value ) ;
402+ const exif = exifData . get ( validFiles [ i ] ) || null ;
403+ void upload ( element , validFiles [ i ] , result . value , exif ) ;
363404 } else {
364405 throw new Error ( result . reason ) ;
365406 }
@@ -394,26 +435,32 @@ export function setup(): void {
394435 return ;
395436 }
396437
397- void resizeImage ( element , file ) . then ( async ( resizeFile ) => {
398- try {
399- const checksum = await getSha256Hash ( resizeFile ) ;
400- const data = await upload ( element , resizeFile , checksum ) ;
401- if ( data === undefined || typeof data . data . attachmentID !== "number" ) {
438+ let exifData : Exif | null ;
439+ void getExifBytes ( file )
440+ . then ( ( exif ) => {
441+ exifData = exif ;
442+ } )
443+ . then ( ( ) => resizeImage ( element , file ) )
444+ . then ( async ( resizeFile ) => {
445+ try {
446+ const checksum = await getSha256Hash ( resizeFile ) ;
447+ const data = await upload ( element , resizeFile , checksum , exifData ) ;
448+ if ( data === undefined || typeof data . data . attachmentID !== "number" ) {
449+ promiseReject ( ) ;
450+ } else {
451+ const attachmentData : AttachmentData = {
452+ attachmentId : data . data . attachmentID ,
453+ url : data . link ,
454+ } ;
455+
456+ promiseResolve ( attachmentData ) ;
457+ }
458+ } catch ( e ) {
402459 promiseReject ( ) ;
403- } else {
404- const attachmentData : AttachmentData = {
405- attachmentId : data . data . attachmentID ,
406- url : data . link ,
407- } ;
408460
409- promiseResolve ( attachmentData ) ;
461+ throw e ;
410462 }
411- } catch ( e ) {
412- promiseReject ( ) ;
413-
414- throw e ;
415- }
416- } ) ;
463+ } ) ;
417464 } ) ;
418465 } ) ;
419466}
0 commit comments