File size: 4,419 Bytes
2bc6d22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import { useState, useCallback, useEffect, useRef } from 'react';
import type { ImageFile, OutputType, CompressionOptions } from '../types';
import { decode, encode, getFileType } from '../utils/imageProcessing';

export function useImageQueue(
  options: CompressionOptions,
  outputType: OutputType,
  setImages: React.Dispatch<React.SetStateAction<ImageFile[]>>
) {
  const MAX_PARALLEL_PROCESSING = 3;
  const [queue, setQueue] = useState<string[]>([]);
  const processingCount = useRef(0);
  const processingImages = useRef(new Set<string>());

  const processImage = useCallback(async (image: ImageFile) => {
    if (processingImages.current.has(image.id)) {
      return; // Skip if already processing this image
    }
    processingImages.current.add(image.id);
    processingCount.current++;

    try {
      setImages((prev) =>
        prev.map((img) =>
          img.id === image.id
            ? { ...img, status: 'processing' as const }
            : img
        )
      );

      const fileBuffer = await image.file.arrayBuffer();
      const sourceType = getFileType(image.file);
      
      if (!fileBuffer.byteLength) {
        throw new Error('Empty file');
      }

      // Decode the image
      const imageData = await decode(sourceType, fileBuffer);
      
      if (!imageData || !imageData.width || !imageData.height) {
        throw new Error('Invalid image data');
      }

      // Encode to the target format
      const compressedBuffer = await encode(outputType, imageData, options);
      
      if (!compressedBuffer.byteLength) {
        throw new Error('Failed to compress image');
      }

      const blob = new Blob([compressedBuffer], { type: `image/${outputType}` });
      const preview = URL.createObjectURL(blob);

      setImages((prev) =>
        prev.map((img) =>
          img.id === image.id
            ? {
                ...img,
                status: 'complete' as const,
                preview,
                blob,
                compressedSize: compressedBuffer.byteLength,
                outputType,
              }
            : img
        )
      );
    } catch (error) {
      console.error('Error processing image:', error);
      setImages((prev) =>
        prev.map((img) =>
          img.id === image.id
            ? {
                ...img,
                status: 'error' as const,
                error: error instanceof Error 
                  ? error.message 
                  : 'Failed to process image',
              }
            : img
        )
      );
    } finally {
      processingImages.current.delete(image.id);
      processingCount.current--;
      // Try to process next images if any
      setTimeout(processNextInQueue, 0);
    }
  }, [options, outputType, setImages]);

  const processNextInQueue = useCallback(() => {
    console.log('Processing next in queue:', {
      queueLength: queue.length,
      processingCount: processingCount.current,
      processingImages: [...processingImages.current]
    });

    if (queue.length === 0) return;

    // Get all images we can process in this batch
    setImages(prev => {
      const imagesToProcess = prev.filter(img => 
        queue.includes(img.id) && 
        !processingImages.current.has(img.id) &&
        processingCount.current < MAX_PARALLEL_PROCESSING
      );

      console.log('Found images to process:', imagesToProcess.length);

      if (imagesToProcess.length === 0) return prev;

      // Start processing these images
      imagesToProcess.forEach((image, index) => {
        setTimeout(() => {
          processImage(image);
        }, index * 100);
      });

      // Remove these from queue
      setQueue(current => current.filter(id => 
        !imagesToProcess.some(img => img.id === id)
      ));

      // Update status to queued
      return prev.map(img => 
        imagesToProcess.some(processImg => processImg.id === img.id)
          ? { ...img, status: 'queued' as const }
          : img
      );
    });
  }, [queue, processImage, setImages]);

  // Start processing when queue changes
  useEffect(() => {
    console.log('Queue changed:', queue.length);
    if (queue.length > 0) {
      processNextInQueue();
    }
  }, [queue, processNextInQueue]);

  const addToQueue = useCallback((imageId: string) => {
    console.log('Adding to queue:', imageId);
    setQueue(prev => [...prev, imageId]);
  }, []);

  return { addToQueue };
}