// 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>

body {
    margin: 0;
    padding: 0;
    overflow: hidden;
}

#board-wrapper {
    width: 100vw;
    height: 100vh;
    overflow: hidden;
    position: relative;
    cursor: grab;
    background-image: url('/static/img/scale_1200.jpg');
    background-repeat: repeat; /* Повторять изображение по горизонтали и вертикали */
    background-size: 1200px 675px; /* Размер повторяющегося блока */
}

#board {
    position: absolute;
    top: 0;
    left: 0;
    transform-origin: 0 0;
}

.note, .image {
    position: absolute;
    background-color: #fff;
    border: 1px solid #ccc;
    box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1);
    padding: 10px;
    box-sizing: border-box;
    cursor: move;
}

textarea {
    width: 100%;
    height: 100%;
    border: none;
    resize: none;
    outline: none;
}

#add-note, #add-image {
    position: fixed;
    bottom: 20px;
    right: 20px;
    padding: 10px 20px;
    background-color: #007bff;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}

#add-image {
    right: 150px;
}

.image img {
    width: 100%;
    height: 100%;
    object-fit: contain; /* Это свойство сохраняет соотношение сторон изображения */
}

.resize-handle {
    position: absolute;
    bottom: 0;
    right: 0;
    width: 10px;
    height: 10px;
    background-color: #007bff;
    cursor: nwse-resize;
}

.hidden {
    visibility: hidden;
}

// 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();
});

// notes.js

// Assumes uploadFile, showContextMenu, addResizeListeners are available globally

socket.on('new_note', (data) => {
    const note = createNoteElement(data);
    document.getElementById('board').appendChild(note);
});

socket.on('updated_note', (data) => {
    const note = document.querySelector(`.note[data-id="${data.id}"]`);
    if (note) {
        note.style.left = `${data.x}px`;
        note.style.top = `${data.y}px`;
        note.style.width = `${data.width}px`;
        note.style.height = `${data.height}px`;
        note.querySelector('textarea').value = data.text;
    }
});

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

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

    const textarea = document.createElement('textarea');
    textarea.value = data.text;
    textarea.addEventListener('input', () => {
        const x = note.offsetLeft;
        const y = note.offsetTop;
        const width = note.offsetWidth;
        const height = note.offsetHeight;
        socket.emit('update_note', { id: data.id, text: textarea.value, x, y, width, height });
    });

    textarea.addEventListener('mousedown', (e) => {
        e.stopPropagation();
    });

    note.appendChild(textarea);

    addNoteEventListeners(note, data.id);

    return note;
}

function addNoteEventListeners(note, id) {
    note.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(note.style.left) || 0;
        const startTop = parseFloat(note.style.top) || 0;

        function moveNote(e) {
            const deltaX = (e.clientX - startX) / scale;
            const deltaY = (e.clientY - startY) / scale;
            note.style.left = `${startLeft + deltaX}px`;
            note.style.top = `${startTop + deltaY}px`;
            const x = parseFloat(note.style.left) || 0;
            const y = parseFloat(note.style.top) || 0;
            const width = note.offsetWidth;
            const height = note.offsetHeight;
            socket.emit('update_note', { id, text: note.querySelector('textarea').value, x, y, width, height });
        }

        document.addEventListener('mousemove', moveNote);

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

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

        function moveNote(e) {
            const deltaX = (e.touches[0].clientX - startX) / scale;
            const deltaY = (e.touches[0].clientY - startY) / scale;
            note.style.left = `${startLeft + deltaX}px`;
            note.style.top = `${startTop + deltaY}px`;
            const x = parseFloat(note.style.left) || 0;
            const y = parseFloat(note.style.top) || 0;
            const width = note.offsetWidth;
            const height = note.offsetHeight;
            socket.emit('update_note', { id, text: note.querySelector('textarea').value, x, y, width, height });
        }

        document.addEventListener('touchmove', moveNote);

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

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

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

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

function handleExistingNotes() {
    document.querySelectorAll('.note').forEach(note => {
        const id = note.dataset.id;
        const text = note.querySelector('textarea').value;
        const x = note.offsetLeft;
        const y = note.offsetTop;
        const width = note.offsetWidth;
        const height = note.offsetHeight;

        note.querySelector('textarea').addEventListener('input', () => {
            const x = note.offsetLeft;
            const y = note.offsetTop;
            const width = note.offsetWidth;
            const height = note.offsetHeight;
            socket.emit('update_note', { id, text: note.querySelector('textarea').value, x, y, width, height });
        });

        note.querySelector('textarea').addEventListener('mousedown', (e) => {
            e.stopPropagation();
        });

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

        addNoteEventListeners(note, id);
    });
}

// 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);
    });
}

// resize.js

function addResizeListeners(element, id, type) {
    const resizeHandle = document.createElement('div');
    resizeHandle.className = 'resize-handle';
    element.appendChild(resizeHandle);

    resizeHandle.addEventListener('mousedown', (e) => {
        if (e.button !== 0) return; // Left mouse button
        e.stopPropagation(); // Prevent triggering drag
        e.preventDefault();
        let startX = e.clientX;
        let startY = e.clientY;
        let startWidth = element.offsetWidth;
        let startHeight = element.offsetHeight;

        function resizeElement(e) {
            const deltaX = e.clientX - startX;
            const deltaY = e.clientY - startY;

            if (type === 'note') {
                element.style.width = `${startWidth + deltaX}px`;
                element.style.height = `${startHeight + deltaY}px`;
            } else if (type === 'image') {
                const ratio = startWidth / startHeight;
                const newWidth = startWidth + deltaX;
                const newHeight = newWidth / ratio;
                element.style.width = `${newWidth}px`;
                element.style.height = `${newHeight}px`;
            }

            const x = element.offsetLeft;
            const y = element.offsetTop;
            const width = element.offsetWidth;
            const height = element.offsetHeight;

            if (type === 'note') {
                socket.emit('update_note', { id, text: element.querySelector('textarea').value, x, y, width, height });
            } else if (type === 'image') {
                socket.emit('update_image', { id, url: element.querySelector('img').src, x, y, width, height });
            }
        }

        document.addEventListener('mousemove', resizeElement);

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

    resizeHandle.addEventListener('touchstart', (e) => {
        e.stopPropagation(); // Prevent triggering drag
        e.preventDefault();
        let startX = e.touches[0].clientX;
        let startY = e.touches[0].clientY;
        let startWidth = element.offsetWidth;
        let startHeight = element.offsetHeight;

        function resizeElement(e) {
            const deltaX = e.touches[0].clientX - startX;
            const deltaY = e.touches[0].clientY - startY;

            if (type === 'note') {
                element.style.width = `${startWidth + deltaX}px`;
                element.style.height = `${startHeight + deltaY}px`;
            } else if (type === 'image') {
                const ratio = startWidth / startHeight;
                const newWidth = startWidth + deltaX;
                const newHeight = newWidth / ratio;
                element.style.width = `${newWidth}px`;
                element.style.height = `${newHeight}px`;
            }

            const x = element.offsetLeft;
            const y = element.offsetTop;
            const width = element.offsetWidth;
            const height = element.offsetHeight;

            if (type === 'note') {
                socket.emit('update_note', { id, text: element.querySelector('textarea').value, x, y, width, height });
            } else if (type === 'image') {
                socket.emit('update_image', { id, url: element.querySelector('img').src, x, y, width, height });
            }
        }

        document.addEventListener('touchmove', resizeElement);

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

Мне надо изменить логику перемещения элемента тачпадом, она должна быть такая:
При длительном нажатии одним пальцем на изображении или заметке (примерно 2 секунды) должен быть активирован режим перемещения элемента и я мог его одним пальцем передвинуть куда надо на доске. Без включенного режима перемещения элементы перемещаться на доске не могут. Также следует учесть что возможны конфликты с обработчиками перемещения самой доски. Данное изменение производится только для тачпада и не должно менять логику управления мышью, обязательно в этом убедись.