Add multiple User Pictures to Task Card when Multiple users are assigned

Hey all, I’m building a Task Management module inside my app and made it so that multiple App users can be assigned a task. When only 1 user is assigned the correct user picture shows up on the card (see #1 on screenshot). When multiple users are assigned no picture shows up (see #2). Is there a way to organize my database so that both pictures show up on the card?

Thanks all!

You would have to find a way to bundle multiple images together using Cloudinary, there’s no easy native way to do it I think.

Create a JavaScript column and put this code:

function calculateDimensions(images, sizeAdjustment, keepRatio, isVertical) {
    let totalWidth = 0;
    let totalHeight = 0;
    const dimensions = [];

    images.forEach(img => {
        let width = img.width;
        let height = img.height;

        if (sizeAdjustment === "reduce") {
            const scale = Math.min(width / img.width, height / img.height);
            width = img.width * scale;
            height = img.height * scale;
        }

        dimensions.push({ width, height });

        if (isVertical) {
            totalWidth = Math.max(totalWidth, width);
            totalHeight += height;
        } else {
            totalWidth += width;
            totalHeight = Math.max(totalHeight, height);
        }
    });

    return {
        totalWidth,
        totalHeight,
        dimensions
    };
}

function mergeImages(imageUrls) {
    return new Promise((resolve, reject) => {
        if (!imageUrls || imageUrls.length < 2) {
            reject('Please provide an array with at least two image URLs to merge');
            return;
        }

        // Create new Image objects to ensure images are fully loaded
        const loadImages = (urls) => {
            return Promise.all(urls.map(url => {
                return new Promise((resolve, reject) => {
                    const img = new Image();
                    img.crossOrigin = "Anonymous";
                    img.onload = () => resolve(img);
                    img.onerror = (event) => reject(new Error(`Failed to load image: ${url}`));
                    img.src = url;
                });
            }));
        };

        loadImages(imageUrls)
            .then(images => {
                // Create a temporary canvas
                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d');
                const isVertical = true;
                const borderWidth = 0;
                const borderColor = "#ffffff";
                const sizeAdjustment = "reduce";
                const keepRatio = true;

                // Calculate dimensions
                let dims = calculateDimensions(images, sizeAdjustment, keepRatio, isVertical);

                // Set canvas size
                if (isVertical) {
                    canvas.width = dims.totalWidth;
                    canvas.height = dims.totalHeight + (borderWidth * (images.length - 1));
                } else {
                    canvas.width = dims.totalWidth + (borderWidth * (images.length - 1));
                    canvas.height = dims.totalHeight;
                }

                // Draw images on the canvas
                let offset = 0;
                images.forEach((img, index) => {
                    const dim = dims.dimensions[index];
                    if (isVertical) {
                        ctx.drawImage(img, 0, offset, dim.width, dim.height);
                        offset += dim.height + borderWidth;
                    } else {
                        ctx.drawImage(img, offset, 0, dim.width, dim.height);
                        offset += dim.width + borderWidth;
                    }

                    if (borderWidth > 0 && index < images.length - 1) {
                        if (isVertical) {
                            ctx.fillStyle = borderColor;
                            ctx.fillRect(0, offset - borderWidth, canvas.width, borderWidth);
                        } else {
                            ctx.fillStyle = borderColor;
                            ctx.fillRect(offset - borderWidth, 0, borderWidth, canvas.height);
                        }
                    }
                });

                // Convert to base64 with compression
                const base64Image = canvas.toDataURL('image/jpeg', 0.7); // Adjust compression quality here

                // Upload to ImgBB
                uploadToImgBB(base64Image)
                    .then(uploadedUrl => resolve(uploadedUrl))
                    .catch(error => reject('Error uploading image: ' + error.message));
            })
            .catch(error => {
                reject('Error merging images: ' + error.message);
            });
    });
}

async function uploadToImgBB(base64Image) {
    const apiUrl = 'https://api.imgbb.com/1/upload';
    const apiKey = p3; // Ensure p3 contains your ImgBB API key
    const imageData = base64Image.split(',')[1]; // Remove the base64 prefix

    const formData = new FormData();
    formData.append('key', apiKey);
    formData.append('image', imageData);
    formData.append('expiration', '600'); // Set expiration to 600 seconds as per your example

    const response = await fetch(apiUrl, {
        method: 'POST',
        body: formData
    });

    if (!response.ok) {
        throw new Error(`Failed to upload image: ${response.statusText}`);
    }

    const result = await response.json();
    if (result.success) {
        return result.data.url;
    } else {
        throw new Error(result.error.message);
    }
}

// Usage example:
const imageUrls = JSON.parse(p1); // Ensure p1 contains a JSON string of image URLs
return await mergeImages(imageUrls)
    .then(uploadedUrl => {
        console.log('Uploaded image URL:', uploadedUrl);
        return uploadedUrl;
    })
    .catch(error => {
        console.error(error);
    });

In p1, put images’ url in the following format: [“url1”,“url2”,“url3”].
In p3, put your imgbb api key. Create a random account because that API key will be visible to everyone. Not a big deal in this case.

Url the returned URL from the Javascript column in your image component:


Source
Base source code from: Merge two images online, heavily modified by chat.mistral.ai.

1 Like

Something like this, @Robert_Petitto has already had a prototype with multiple circles next to each other but I can’t seem to find the keyword for that post.

I also have an experimental code column: https://rpetitto.github.io/avatars

2 Likes