// images.js

// Assumes uploadFile, showContextMenu, addResizeListeners are available globally

socket.on('new_image', (data) => {
    const image = createImageElement(data);
    document.getElementById('board').appendChild(image);
});

socket.on('updated_image', (data) => {
    const image = document.querySelector(`.image[data-id="${data.id}"]`);
    if (image) {
        image.style.left = `${data.x}px`;
        image.style.top = `${data.y}px`;
        image.style.width = `${data.width}px`;
        image.style.height = `${data.height}px`;
        image.querySelector('img').src = data.url;
    }
});

socket.on('element_deleted', (data) => {
    if (data.type === 'image') {
        const element = document.querySelector(`.image[data-id="${data.id}"]`);
        if (element) {
            element.remove();
        }
    }
});

function createImageElement(data) {
    const image = document.createElement('div');
    image.className = 'image';
    image.dataset.id = data.id;
    image.style.left = `${data.x}px`;
    image.style.top = `${data.y}px`;
    image.style.width = `${data.width}px`;
    image.style.height = `${data.height}px`;

    const img = document.createElement('img');
    img.src = data.url;
    img.style.width = '100%';
    img.style.height = '100%';
    img.style.objectFit = 'contain'; // Maintains aspect ratio

    image.appendChild(img);

    addImageEventListeners(image, data.id);

    return image;
}

function addImageEventListeners(image, id) {
    image.addEventListener('mousedown', (e) => {
        if (e.button !== 0) return; // Left mouse button
        e.preventDefault();
        e.stopPropagation();
        const startX = e.clientX;
        const startY = e.clientY;
        const startLeft = parseFloat(image.style.left) || 0;
        const startTop = parseFloat(image.style.top) || 0;

        function moveImage(e) {
            const deltaX = (e.clientX - startX) / scale;
            const deltaY = (e.clientY - startY) / scale;
            image.style.left = `${startLeft + deltaX}px`;
            image.style.top = `${startTop + deltaY}px`;
            const x = parseFloat(image.style.left) || 0;
            const y = parseFloat(image.style.top) || 0;
            const width = image.offsetWidth;
            const height = image.offsetHeight;
            socket.emit('update_image', { id, url: image.querySelector('img').src, x, y, width, height });
        }

        document.addEventListener('mousemove', moveImage);

        document.addEventListener('mouseup', () => {
            document.removeEventListener('mousemove', moveImage);
        }, { once: true });
    });

    image.addEventListener('touchstart', (e) => {
        e.preventDefault();
        e.stopPropagation();
        const startX = e.touches[0].clientX;
        const startY = e.touches[0].clientY;
        const startLeft = parseFloat(image.style.left) || 0;
        const startTop = parseFloat(image.style.top) || 0;

        function moveImage(e) {
            const deltaX = (e.touches[0].clientX - startX) / scale;
            const deltaY = (e.touches[0].clientY - startY) / scale;
            image.style.left = `${startLeft + deltaX}px`;
            image.style.top = `${startTop + deltaY}px`;
            const x = parseFloat(image.style.left) || 0;
            const y = parseFloat(image.style.top) || 0;
            const width = image.offsetWidth;
            const height = image.offsetHeight;
            socket.emit('update_image', { id, url: image.querySelector('img').src, x, y, width, height });
        }

        document.addEventListener('touchmove', moveImage);

        document.addEventListener('touchend', () => {
            document.removeEventListener('touchmove', moveImage);
        }, { once: true });
    });

    image.addEventListener('dblclick', (e) => {
        e.preventDefault();
        e.stopPropagation();
        showContextMenu(image, id, e.clientX, e.clientY);
    });

    image.addEventListener('touchstart', (e) => {
        if (e.touches.length === 2) {
            e.preventDefault();
            e.stopPropagation();
            showContextMenu(image, id, e.touches[0].clientX, e.touches[0].clientY);
        }
    });

    addResizeListeners(image, id, 'image');
}

function handleExistingImages() {
    document.querySelectorAll('.image').forEach(image => {
        const id = image.dataset.id;
        const url = image.querySelector('img').src;
        const x = image.offsetLeft;
        const y = image.offsetTop;
        const width = image.offsetWidth;
        const height = image.offsetHeight;

        image.addEventListener('dblclick', (e) => {
            e.preventDefault();
            e.stopPropagation();
            showContextMenu(image, id, e.clientX, e.clientY);
        });

        addImageEventListeners(image, id);
    });
}

// board.js

let scale = 0.5; // Initial scale
let translateX = 0;
let translateY = 0;

const board = document.getElementById('board');
const boardWrapper = document.getElementById('board-wrapper');

// Constants for zoom
const MIN_SCALE = 0.1; // Minimum scale
const MAX_SCALE = 5;   // Maximum scale
const ZOOM_FACTOR = 0.1; // Zoom speed

// Apply initial scale
board.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
boardWrapper.style.backgroundPosition = `${translateX}px ${translateY}px`;

// Show images after setting scale
document.querySelectorAll('.image').forEach(img => {
    img.classList.remove('hidden');
});

boardWrapper.addEventListener('wheel', (e) => {
    e.preventDefault();

    const previousScale = scale;
    scale += e.deltaY < 0 ? ZOOM_FACTOR : -ZOOM_FACTOR;
    scale = Math.min(Math.max(MIN_SCALE, scale), MAX_SCALE);

    // Calculate cursor position relative to the board
    const rect = board.getBoundingClientRect();
    const offsetX = (e.clientX - rect.left) / previousScale;
    const offsetY = (e.clientY - rect.top) / previousScale;

    // Adjust translate to keep cursor point at the same board position
    translateX -= offsetX * (scale - previousScale);
    translateY -= offsetY * (scale - previousScale);

    board.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
    boardWrapper.style.backgroundPosition = `${translateX}px ${translateY}px`;
});

boardWrapper.addEventListener('touchstart', (e) => {
    if (e.touches.length === 2) {
        e.preventDefault();
        const touch1 = e.touches[0];
        const touch2 = e.touches[1];
        const startDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);
        const startScale = scale;

        function zoom(e) {
            if (e.touches.length !== 2) return;
            const touch1 = e.touches[0];
            const touch2 = e.touches[1];
            const currentDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);
            const newScale = startScale * (currentDistance / startDistance);
            scale = Math.min(Math.max(MIN_SCALE, newScale), MAX_SCALE);

            // Calculate midpoint of touches
            const midX = (touch1.clientX + touch2.clientX) / 2;
            const midY = (touch1.clientY + touch2.clientY) / 2;

            // Calculate offset relative to previous scale
            const rect = board.getBoundingClientRect();
            const offsetX = (midX - rect.left) / startScale;
            const offsetY = (midY - rect.top) / startScale;

            // Adjust translate to keep midpoint at same board position
            translateX -= offsetX * (scale - startScale);
            translateY -= offsetY * (scale - startScale);

            board.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
            boardWrapper.style.backgroundPosition = `${translateX}px ${translateY}px`;
        }

        document.addEventListener('touchmove', zoom);

        document.addEventListener('touchend', () => {
            document.removeEventListener('touchmove', zoom);
        }, { once: true });
    }
});

let isPanning = false;
let startPanX, startPanY;

boardWrapper.addEventListener('mousedown', (e) => {
    if (e.button === 2) { // Right mouse button
        isPanning = true;
        startPanX = e.clientX - translateX;
        startPanY = e.clientY - translateY;
        boardWrapper.style.cursor = 'grabbing';
    }
});

boardWrapper.addEventListener('mouseup', (e) => {
    if (e.button === 2) { // Right mouse button
        isPanning = false;
        boardWrapper.style.cursor = 'grab';
    }
});

boardWrapper.addEventListener('mousemove', (e) => {
    if (!isPanning) return;
    e.preventDefault();
    translateX = e.clientX - startPanX;
    translateY = e.clientY - startPanY;
    board.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
    boardWrapper.style.backgroundPosition = `${translateX}px ${translateY}px`;
});

// Add touch events for panning
boardWrapper.addEventListener('touchstart', (e) => {
    if (e.touches.length === 1) {
        isPanning = true;
        startPanX = e.touches[0].clientX - translateX;
        startPanY = e.touches[0].clientY - translateY;
        boardWrapper.style.cursor = 'grabbing';
    }
});

boardWrapper.addEventListener('touchend', (e) => {
    isPanning = false;
    boardWrapper.style.cursor = 'grab';
});

boardWrapper.addEventListener('touchmove', (e) => {
    if (!isPanning) return;
    e.preventDefault();
    translateX = e.touches[0].clientX - startPanX;
    translateY = e.touches[0].clientY - startPanY;
    board.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
    boardWrapper.style.backgroundPosition = `${translateX}px ${translateY}px`;
});

// Prevent context menu on board-wrapper
boardWrapper.addEventListener('contextmenu', (e) => {
    e.preventDefault();
});

// static/js/main.js

// Проверяем, что 'socket' уже инициализирован
if (!window.socket) {
    console.error('Socket is not initialized!');
}

// Event listener для кнопки 'add-note'
document.getElementById('add-note').addEventListener('click', () => {
    const x = Math.floor(Math.random() * window.innerWidth);
    const y = Math.floor(Math.random() * window.innerHeight);
    const width = 200; // Ширина по умолчанию
    const height = 40; // Высота по умолчанию
    socket.emit('create_note', { text: 'New Comment', x, y, width, height });
});

// Event listener для кнопки 'add-image'
document.getElementById('add-image').addEventListener('click', () => {
    const formHtml = `
        <div id="image-form" style="position:fixed; top:50%; left:50%; transform:translate(-50%, -50%); background:white; padding:20px; border-radius:8px; box-shadow:0 0 10px rgba(0,0,0,0.1); z-index: 1000;">
            <label for="image-url" style="display:block; margin-bottom:8px;">Image URL:</label>
            <input type="text" id="image-url" placeholder="Enter image URL" style="width: calc(100% - 22px); padding:10px; margin-bottom:12px; border:1px solid #ccc; border-radius:4px;"><br>
            <label for="image-file" style="display:block; margin-bottom:8px;">Or Upload File:</label>
            <input type="file" id="image-file" accept="image/*" style="margin-bottom:12px;"><br>
            <button id="add-image-submit" style="padding:10px 20px; background-color:#007bff; color:white; border:none; border-radius:4px; cursor:pointer; margin-right:8px;">Add Image</button>
            <button id="cancel-image" style="padding:10px 20px; background-color:#ccc; color:white; border:none; border-radius:4px; cursor:pointer;">Cancel</button>
        </div>
    `;
    document.body.insertAdjacentHTML('beforeend', formHtml);

    document.getElementById('add-image-submit').addEventListener('click', async () => {
        const url = document.getElementById('image-url').value;
        const fileInput = document.getElementById('image-file');
        let imageUrl;

        if (url) {
            imageUrl = url;
        } else if (fileInput.files.length > 0) {
            const file = fileInput.files[0];
            imageUrl = await uploadFile(file);
        }

        if (imageUrl) {
            const x = Math.floor(Math.random() * window.innerWidth);
            const y = Math.floor(Math.random() * window.innerHeight);
            const img = new Image();
            img.src = imageUrl;
            img.onload = () => {
                const maxWidth = 720;
                const maxHeight = 720;
                let width = img.naturalWidth;
                let height = img.naturalHeight;

                if (width > maxWidth || height > maxHeight) {
                    const ratio = Math.min(maxWidth / width, maxHeight / height);
                    width *= ratio;
                    height *= ratio;
                }

                socket.emit('create_image', { url: imageUrl, x, y, width, height });
                closeModal();
            };
        }
    });

    document.getElementById('cancel-image').addEventListener('click', closeModal);

    function closeModal() {
        const form = document.getElementById('image-form');
        if (form) {
            form.remove();
        }
    }
});

// Обработка существующих заметок и изображений после загрузки страницы
document.addEventListener('DOMContentLoaded', () => {
    handleExistingNotes();
    handleExistingImages();
});

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Miro Clone</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
    <div id="board-wrapper">
        <div id="board">
            {% for note in notes %}
            <div class="note" data-id="{{ note.id }}" style="left: {{ note.x }}px; top: {{ note.y }}px; width: {{ note.width }}px; height: {{ note.height }}px;">
                <textarea>{{ note.text }}</textarea>
            </div>
            {% endfor %}
            {% for image in images %}
            <div class="image hidden" data-id="{{ image.id }}" style="left: {{ image.x }}px; top: {{ image.y }}px; width: {{ image.width }}px; height: {{ image.height }}px;">
                <img src="{{ image.url }}" style="width: 100%; height: 100%; object-fit: contain;">
            </div>
            {% endfor %}
        </div>
    </div>
    <button id="add-note">Add Comment</button>
    <button id="add-image">Add Image</button>

    <!-- Подключение Socket.io -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>

    <!-- Инициализация Socket -->
    <script src="{{ url_for('static', filename='js/socket.js') }}"></script>

    <!-- Другие JavaScript файлы -->
    <script src="{{ url_for('static', filename='js/upload.js') }}"></script>
    <script src="{{ url_for('static', filename='js/contextMenu.js') }}"></script>
    <script src="{{ url_for('static', filename='js/resize.js') }}"></script>
    <script src="{{ url_for('static', filename='js/notes.js') }}"></script>
    <script src="{{ url_for('static', filename='js/images.js') }}"></script>
    <script src="{{ url_for('static', filename='js/board.js') }}"></script>
    <script src="{{ url_for('static', filename='js/main.js') }}"></script>
</body>
</html>

// contextMenu.js

function showContextMenu(element, id, x, y) {
    const contextMenu = document.createElement('div');
    contextMenu.className = 'context-menu';
    contextMenu.style.position = 'absolute';
    contextMenu.style.left = `${x}px`;
    contextMenu.style.top = `${y}px`;
    contextMenu.style.backgroundColor = 'white';
    contextMenu.style.borderRadius = '4px';
    contextMenu.style.border = '1px solid #ccc';
    contextMenu.style.boxShadow = '0 0 10px rgba(0,0,0,0.1)';
    contextMenu.style.zIndex = '1000';

    const deleteButton = document.createElement('button');
    deleteButton.textContent = 'Delete';
    deleteButton.style.padding = '5px 10px';
    deleteButton.style.border = 'none';
    deleteButton.style.backgroundColor = '#ff4d4d';
    deleteButton.style.color = 'white';
    deleteButton.style.borderRadius = '4px';
    deleteButton.style.cursor = 'pointer';
    deleteButton.style.width = '100%';
    deleteButton.style.textAlign = 'left';

    deleteButton.addEventListener('click', () => {
        element.remove();
        socket.emit('delete_element', { id: id, type: element.classList.contains('note') ? 'note' : 'image' });
        contextMenu.remove();
    });

    contextMenu.appendChild(deleteButton);
    document.body.appendChild(contextMenu);

    // Close context menu when clicking outside
    document.addEventListener('click', (e) => {
        if (!contextMenu.contains(e.target)) {
            contextMenu.remove();
        }
    }, { once: true });
}

Помоги мне. Мне надо чтобы на устройствах с тачпадом в контекстном меню изображения была еще кнопка "Move" и при нажатии на которую включался режим перемещения элемента. Пока на нее не нажать элемент не должен перемещаться. Это нужно только для тачпада, для мыши все оставляем как сейчас есть. Убедись что логика управления мышью не нарушена