// iframe.js

socket.on('new_iframe', (data) => {
    const iframe = createIframeElement(data);
    document.getElementById('board').appendChild(iframe);
});

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

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

// Event listener for 'add-iframe' button
document.getElementById('add-iframe').addEventListener('click', () => {
    const formHtml = `<div id="iframe-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="iframe-url" style="display:block; margin-bottom:8px;">Iframe URL:</label>
        <input type="text" id="iframe-url" placeholder="Enter iframe URL" style="width: calc(100% - 22px); padding:10px; margin-bottom:12px; border:1px solid #ccc; border-radius:4px;"><br>
        <button id="add-iframe-submit" style="padding:10px 20px; background-color:#007bff; color:white; border:none; border-radius:4px; cursor:pointer; margin-right:8px;">Add Iframe</button>
        <button id="cancel-iframe" 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-iframe-submit').addEventListener('click', () => {
        const url = document.getElementById('iframe-url').value;
        if (url) {
            addIframeToCenter({ url });
            closeModal();
        }
    });

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

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

function addIframeToCenter(iframeData) {
    const width = 800; // default width
    const height = 925; // default height

    // Get visible area dimensions
    const rect = boardWrapper.getBoundingClientRect();
    console.log(rect);
    // Calculate center coordinates considering current scale and translation
    const centerX = ((rect.width / 2 - translateX) / scale) - width / 2;
    const centerY = ((rect.height / 2 - translateY) / scale) - height / 2;

    socket.emit('create_iframe', {
        name: 'Iframe',
        url: iframeData.url,
        x: centerX,
        y: centerY,
        width,
        height,
        board_id: currentBoardId
    });
}

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

    const iframe = document.createElement('iframe');
    iframe.src = data.url;
    iframe.style.width = '100%';
    iframe.style.height = '100%';
    iframe.style.border = 'none';

    iframeContainer.appendChild(iframe);

    addIframeEventListeners(iframeContainer, data.id);

    return iframeContainer;
}

function addIframeEventListeners(iframeContainer, id) {
    let isTouchDragEnabled = false;

    iframeContainer.addEventListener('mousedown', (e) => {
        if (e.button !== 0) return; // Only respond to left mouse button
        e.preventDefault();
        e.stopPropagation();

        const startX = e.clientX;
        const startY = e.clientY;
        const startLeft = parseFloat(iframeContainer.style.left) || 0;
        const startTop = parseFloat(iframeContainer.style.top) || 0;

        function moveIframe(e) {
            const deltaX = (e.clientX - startX) / scale;
            const deltaY = (e.clientY - startY) / scale;
            iframeContainer.style.left = `${startLeft + deltaX}px`;
            iframeContainer.style.top = `${startTop + deltaY}px`;

            const x = parseFloat(iframeContainer.style.left) || 0;
            const y = parseFloat(iframeContainer.style.top) || 0;
            const width = iframeContainer.offsetWidth;
            const height = iframeContainer.offsetHeight;

            socket.emit('update_iframe', { id, x, y, width, height });
        }

        document.addEventListener('mousemove', moveIframe);

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

    function startTouchDrag(e) {
        e.preventDefault();
        e.stopPropagation();

        const startX = e.touches[0].clientX;
        const startY = e.touches[0].clientY;
        const startLeft = parseFloat(iframeContainer.style.left) || 0;
        const startTop = parseFloat(iframeContainer.style.top) || 0;

        function moveTouch(e) {
            const deltaX = (e.touches[0].clientX - startX) / scale;
            const deltaY = (e.touches[0].clientY - startY) / scale;
            iframeContainer.style.left = `${startLeft + deltaX}px`;
            iframeContainer.style.top = `${startTop + deltaY}px`;

            const x = parseFloat(iframeContainer.style.left) || 0;
            const y = parseFloat(iframeContainer.style.top) || 0;
            const width = iframeContainer.offsetWidth;
            const height = iframeContainer.offsetHeight;

            socket.emit('update_iframe', { id, x, y, width, height });
        }

        function endTouchDrag() {
            document.removeEventListener('touchmove', moveTouch);
            document.removeEventListener('touchend', endTouchDrag);
        }

        document.addEventListener('touchmove', moveTouch);
        document.addEventListener('touchend', endTouchDrag);
    }

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

        // Toggle touch drag
        isTouchDragEnabled = !isTouchDragEnabled;

        if (isTouchDragEnabled) {
            iframeContainer.addEventListener('touchstart', startTouchDrag);
            document.addEventListener('mousedown', checkClickOutside);
        } else {
            iframeContainer.removeEventListener('touchstart', startTouchDrag);
            document.removeEventListener('mousedown', checkClickOutside);
        }
    });

    function checkClickOutside(e) {
        if (!iframeContainer.contains(e.target)) {
            iframeContainer.removeEventListener('touchstart', startTouchDrag);
            isTouchDragEnabled = false;
            document.removeEventListener('mousedown', checkClickOutside);
        }
    }

    addResizeListeners(iframeContainer, id, 'iframe');
}

function handleExistingIframes() {
    document.querySelectorAll('.iframe').forEach(iframeContainer => {
        const id = iframeContainer.dataset.id;
        addIframeEventListeners(iframeContainer, id);
    });
}

socket.on('new_audio', (data) => {
    const audioElement = createAudioElement(data);
    document.getElementById('board').appendChild(audioElement);
});

socket.on('updated_audio', (data) => {
    const audioElement = document.querySelector(`.audio[data-id="${data.id}"]`);
    if (audioElement) {
        audioElement.style.left = `${data.x}px`;
        audioElement.style.top = `${data.y}px`;
        audioElement.style.width = `${data.width}px`;
        audioElement.style.height = `${data.height}px`;
        audioElement.querySelector('.audio-file-name').textContent = data.file_name;
        audioElement.querySelector('audio').src = data.url;
    }
});

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

function createAudioElement(data) {
    // Создаем основной контейнер для аудио элемента
    const audioElement = document.createElement('div');
    audioElement.className = 'audio';
    audioElement.dataset.id = data.id;
    audioElement.style.left = `${data.x}px`;
    audioElement.style.top = `${data.y}px`;
    audioElement.style.width = `${data.width}px`;
    audioElement.style.height = `${data.height}px`;

    // Элемент для отображения имени файла
    const audioTitle = document.createElement('div');
    audioTitle.className = 'audio-file-name';
    audioTitle.textContent = data.file_name;

    // Сам аудио элемент
    const audioTag = document.createElement('audio');
    audioTag.src = data.url;

    // Контейнер для управляющих кнопок
    const controls = document.createElement('div');
    controls.className = 'controls';

    // Кнопка проигрывания/паузы
    const playPauseButton = document.createElement('button');
    playPauseButton.className = 'play-pause';
    playPauseButton.textContent = '▶️';

    // Кнопка остановки
    const stopButton = document.createElement('button');
    stopButton.className = 'stop';
    stopButton.textContent = '⏹️';

    // Полоса прогресса
    const progressBar = document.createElement('input');
    progressBar.className = 'progress-bar';
    progressBar.type = 'range';
    progressBar.min = 0;
    progressBar.value = 0;
    progressBar.step = 0.1;

    // Добавляем элементы управления в контейнер
    controls.appendChild(playPauseButton);
    controls.appendChild(stopButton);
    controls.appendChild(progressBar);

    // Добавляем все подэлементы в основной контейнер
    audioElement.appendChild(audioTitle);
    audioElement.appendChild(audioTag);
    audioElement.appendChild(controls);

    // Привязываем необходимые обработчики событий сразу после создания элементов
    attachAudioEventListeners(audioElement, audioTag, playPauseButton, stopButton, progressBar);
    addAudioEventListeners(audioElement, data.id); // Для перетаскивания, изменения размера и др.
    return audioElement;
}

function attachAudioEventListeners(audioElement, audioTag, playPauseButton, stopButton, progressBar) {
    // Событие, когда аудио метаданные загружены
    audioTag.addEventListener('loadedmetadata', () => {
        progressBar.max = audioTag.duration;
    });

    // Событие обновления времени проигрывания аудио
    audioTag.addEventListener('timeupdate', () => {
        progressBar.value = audioTag.currentTime;
    });

    // Обработчик клика для кнопки проигрывания/паузы
    playPauseButton.addEventListener('click', () => {
        if (audioTag.paused) {
            audioTag.play();
            playPauseButton.textContent = '⏸️';
        } else {
            audioTag.pause();
            playPauseButton.textContent = '▶️';
        }
    });

    // Обработчик клика для кнопки остановки
    stopButton.addEventListener('click', () => {
        audioTag.pause();
        audioTag.currentTime = 0;
        playPauseButton.textContent = '▶️';
    });

    // Обработчик изменения значения полосы прогресса
    progressBar.addEventListener('input', () => {
        audioTag.currentTime = progressBar.value;
    });
}

function addAudioEventListeners(audioElement, id) {
    // Обработчик для перетаскивания элемента
    audioElement.addEventListener('mousedown', (e) => {
        // Проверяем, что клик не на элементах управления
        if (e.target.closest('.play-pause') || e.target.closest('.stop') || e.target.closest('.progress-bar')) {
            return;
        }

        if (e.button !== 0) return;
        e.preventDefault();
        e.stopPropagation();

        const startX = e.clientX;
        const startY = e.clientY;
        const startLeft = parseFloat(audioElement.style.left) || 0;
        const startTop = parseFloat(audioElement.style.top) || 0;

        function moveAudio(e) {
            const deltaX = (e.clientX - startX) / scale;
            const deltaY = (e.clientY - startY) / scale;
            audioElement.style.left = `${startLeft + deltaX}px`;
            audioElement.style.top = `${startTop + deltaY}px`;
            const x = parseFloat(audioElement.style.left) || 0;
            const y = parseFloat(audioElement.style.top) || 0;
            const width = audioElement.offsetWidth;
            const height = audioElement.offsetHeight;
            socket.emit('update_audio', { id, x, y, width, height });
        }

        // Добавляем обработчик движения мыши
        document.addEventListener('mousemove', moveAudio);

        // Обработчик отпускания кнопки мыши
        document.addEventListener('mouseup', () => {
            document.removeEventListener('mousemove', moveAudio);
        }, { once: true });
    });

    // Обработчик двойного клика
    audioElement.addEventListener('dblclick', (e) => {
        e.preventDefault();
        e.stopPropagation();
        showContextMenu(audioElement, id, e.clientX, e.clientY);
    });

    // Для добавления других нужных слушателей, таких как изменение размера
    addResizeListeners(audioElement, id, 'audio');
}

document.getElementById('add-audio').addEventListener('click', () => {
    const formHtml = `
        <div id="audio-form" style="position:fixed; top:50%; left:50%; transform:translate(-50%, -50%); background:#f9f9f9; padding:20px; border-radius:12px; box-shadow:0 15px 30px rgba(0,0,0,0.2); z-index: 1000; max-width:400px; width:100%; text-align:center; font-family:'Arial', sans-serif;">
            <label for="audio-file" style="display:block; margin-bottom:16px; font-size:16px; color:#333;">🎵 Upload Your Audio File</label>
            <input type="file" id="audio-file" accept="audio/*" style="margin-bottom:20px; width:calc(100% - 32px); padding:10px; border-radius:8px; background-color:#fff; border:1px solid #ddd; color:#333; cursor:pointer;">
            <br>
            <button id="add-audio-submit" style="padding:12px 24px; background-color:#28a745; color:white; border:none; border-radius:6px; cursor:pointer; margin-right:12px; box-shadow:0 4px 8px rgba(40,167,69,0.2); transition:background-color 0.3s;">Add Audio</button>
            <button id="cancel-audio" style="padding:12px 24px; background-color:#ff3860; color:white; border:none; border-radius:6px; cursor:pointer; box-shadow:0 4px 8px rgba(255,56,96,0.2); transition:background-color 0.3s;">Cancel</button>
        </div>
    `;
    document.body.insertAdjacentHTML('beforeend', formHtml);

    document.getElementById('add-audio-submit').addEventListener('click', async () => {
        const fileInput = document.getElementById('audio-file');
        if (fileInput.files.length > 0) {
            const file = fileInput.files[0];
            const audioData = await uploadFile(file, '/upload_audio');
            addAudioToCenter(audioData);
            closeModal();
        }
    });

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

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

function addAudioToCenter(audioData) {
    const rect = boardWrapper.getBoundingClientRect();
    const width = 200;
    const height = 80;

    const centerX = ((rect.width / 2 - translateX) / scale) - width / 2;
    const centerY = ((rect.height / 2 - translateY) / scale) - height / 2;

    socket.emit('create_audio', {
        file_name: audioData.file_name,
        url: audioData.url,
        x: centerX,
        y: centerY,
        width: 500,
        height: 100,
        board_id: currentBoardId
    });
}

function handleExistingAudioElements() {
    // Находим все существующие элементы с классом 'audio'
    document.querySelectorAll('.audio').forEach(audioElement => {
        // Получаем audioTag и элементы управления, которые нам необходимы для привязки обработчиков
        const audioTag = audioElement.querySelector('audio');
        const playPauseButton = audioElement.querySelector('.play-pause');
        const stopButton = audioElement.querySelector('.stop');
        const progressBar = audioElement.querySelector('.progress-bar');

        const id = audioElement.dataset.id; // Получаем идентификатор элемента

        // Привязываем события, которые отвечают за взаимодействие с непосредственно аудио
        attachAudioEventListeners(audioElement, audioTag, playPauseButton, stopButton, progressBar);

        // Привязываем другие события, такие как перетаскивание и изменение размера
        addAudioEventListeners(audioElement, id);
    });
}

// static/js/main.js

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

document.addEventListener('DOMContentLoaded', function() {
    const menuButton = document.getElementById('menu-button');
    const dropdownMenu = document.getElementById('dropdown-menu');

    menuButton.addEventListener('click', () => {
        dropdownMenu.classList.toggle('hidden');
        dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block';
    });

    // Optional: Close the dropdown menu if the user clicks outside of it
    document.addEventListener('click', (event) => {
        if (!menuButton.contains(event.target) && !dropdownMenu.contains(event.target)) {
            dropdownMenu.style.display = 'none';
        }
    });
});

document.getElementById('toggle-vr').addEventListener('click', () => {
    const vrWrapper = document.getElementById('vr-wrapper');

    if (vrWrapper) {
        vrWrapper.remove();
        boardWrapper.style.display = 'block'; // Показать оригинальную доску
    } else {
        // Создание контейнера VR
        const newVrWrapper = document.createElement('div');
        newVrWrapper.id = 'vr-wrapper';

        const leftHalf = document.createElement('div');
        leftHalf.className = 'half-screen';
        const rightHalf = document.createElement('div');
        rightHalf.className = 'half-screen';

        const boardWrapperCloneLeft = boardWrapper.cloneNode(true);
        const boardLeft = boardWrapperCloneLeft.firstElementChild;

        const boardWrapperCloneRight = boardWrapper.cloneNode(true);
        const boardRight = boardWrapperCloneRight.firstElementChild;

        // Функция для обновления трансформации
        const applyTransformShift = (element, shiftX) => {
            const currentTransform = element.style.transform || '';

            let newTransform;
            let translateXMatch = currentTransform.match(/translateX\(([-\d.]+)px\)/);
            let translateMatch = currentTransform.match(/translate\(([-\d.]+)px,\s*([-\d.]+)px\)/);

            if (translateXMatch) {
                const currentTranslateX = parseFloat(translateXMatch[1]);
                const newTranslateX = currentTranslateX + shiftX;
                newTransform = currentTransform.replace(/translateX\(([-\d.]+)px\)/, `translateX(${newTranslateX}px)`);
            } else if (translateMatch) {
                const currentTranslateX = parseFloat(translateMatch[1]);
                const currentTranslateY = parseFloat(translateMatch[2]);
                const newTranslateX = currentTranslateX + shiftX;
                newTransform = currentTransform.replace(/translate\(([-\d.]+)px,\s*([-\d.]+)px\)/, `translate(${newTranslateX}px, ${currentTranslateY}px)`);
            } else {
                newTransform = `translateX(${shiftX}px) ${currentTransform}`.trim();
            }

            element.style.transform = newTransform;
        };

        if (boardLeft) {
            boardLeft.id = 'board-left';
            applyTransformShift(boardLeft, 54); // Смещаем board-left
        }

        if (boardRight) {
            boardRight.id = 'board-right';
            applyTransformShift(boardRight, -54);

            const images = boardRight.getElementsByTagName('img');
            Array.from(images).forEach(img => {
                const originalSrc = img.src;
                const imageName = originalSrc.split('/').pop();
                const newSrc = `/static/uploads/images/vr/${imageName}`;

                fetch(newSrc, { method: 'HEAD' })
                    .then(response => {
                        if (response.ok) {
                            img.src = newSrc; // Изображение существует, обновляем источник
                        }
                    });
            });
        }

        leftHalf.appendChild(boardWrapperCloneLeft);
        rightHalf.appendChild(boardWrapperCloneRight);

        newVrWrapper.appendChild(leftHalf);
        newVrWrapper.appendChild(rightHalf);
        document.body.appendChild(newVrWrapper);

        boardWrapper.style.display = 'none'; // Скрыть оригинальную доску
    }
});

// Event listener для кнопки 'add-note'
document.getElementById('add-note').addEventListener('click', () => {
    // Получаем размеры видимой области
    const rect = boardWrapper.getBoundingClientRect();

    const width = 200; // Ширина по умолчанию
    const height = 40; // Высота по умолчанию

    // Рассчитываем координаты центра видимой области с учётом текущего масштаба и смещения
    const centerX = ((rect.width / 2 - translateX) / scale) - width / 2;
    const centerY = ((rect.height / 2 - translateY) / scale) - height / 2;


    socket.emit('create_note', { text: 'New Comment', x: centerX, y: centerY, width, height, board_id: currentBoardId });
});

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

        if (url) {
            imageData = await fetchLocalImageUrl(url);
        } else if (fileInput.files.length > 0) {
            const file = fileInput.files[0];
            imageData = await uploadFile(file, '/upload');
        }

        if (imageData) {
            addImageToCenter(imageData);
            closeModal();
        }
    });

    async function fetchLocalImageUrl(imageUrl) {
    const response = await fetch('/fetch_image_from_url', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({ url: imageUrl })
    });

    return response.json();
    }

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

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



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

// board.js

let scale = 0.05; // 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.05; // 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();
});

import uuid
import io
from urllib.parse import urlparse

import requests
from flask import Flask, render_template, request, jsonify, flash, redirect, url_for
from flask_socketio import SocketIO, emit
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin, LoginManager, login_user, login_required, logout_user, current_user
from PIL import Image as PILImage
from werkzeug.security import generate_password_hash, check_password_hash
from forms import RegistrationForm, LoginForm
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db'
db = SQLAlchemy(app)
socketio = SocketIO(app)

login_manager = LoginManager(app)
login_manager.login_view = 'login'

UPLOAD_IMAGE_FOLDER = 'static/uploads/images'
UPLOAD_IMAGE_VR_FOLDER = 'static/uploads/images/vr'
UPLOAD_AUDIO_FOLDER = 'static/uploads/audio'
app.config['UPLOAD_IMAGE_FOLDER'] = UPLOAD_IMAGE_FOLDER
app.config['UPLOAD_IMAGE_VR_FOLDER'] = UPLOAD_IMAGE_VR_FOLDER
app.config['UPLOAD_AUDIO_FOLDER'] = UPLOAD_AUDIO_FOLDER


class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(150), unique=True, nullable=False)
    email = db.Column(db.String(150), unique=True, nullable=False)
    password = db.Column(db.String(150), nullable=False)
    boards = db.relationship('Board', backref='owner', lazy=True)


class Board(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(150), nullable=False)
    is_public = db.Column(db.Boolean, default=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)


class Note(db.Model):
    board_id = db.Column(db.Integer, db.ForeignKey('board.id'), nullable=False)
    id = db.Column(db.Integer, primary_key=True)
    text = db.Column(db.String(500), nullable=False)
    x = db.Column(db.Integer, nullable=False)
    y = db.Column(db.Integer, nullable=False)
    width = db.Column(db.Integer, nullable=False)
    height = db.Column(db.Integer, nullable=False)


class Image(db.Model):
    board_id = db.Column(db.Integer, db.ForeignKey('board.id'), nullable=False)
    id = db.Column(db.Integer, primary_key=True)
    url = db.Column(db.String(500), nullable=False)
    x = db.Column(db.Integer, nullable=False)
    y = db.Column(db.Integer, nullable=False)
    width = db.Column(db.Integer, nullable=False)
    height = db.Column(db.Integer, nullable=False)


class Audio(db.Model):
    board_id = db.Column(db.Integer, db.ForeignKey('board.id'), nullable=False)
    id = db.Column(db.Integer, primary_key=True)
    file_name = db.Column(db.String(500), nullable=False)
    url = db.Column(db.String(500), nullable=False)
    x = db.Column(db.Integer, nullable=False)
    y = db.Column(db.Integer, nullable=False)
    width = db.Column(db.Integer, nullable=False)
    height = db.Column(db.Integer, nullable=False)


class Link(db.Model):
    board_id = db.Column(db.Integer, db.ForeignKey('board.id'), nullable=False)
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(150), nullable=False)
    url = db.Column(db.String(500), nullable=False)
    x = db.Column(db.Integer, nullable=False)
    y = db.Column(db.Integer, nullable=False)
    width = db.Column(db.Integer, nullable=False)
    height = db.Column(db.Integer, nullable=False)


class Iframe(db.Model):
    board_id = db.Column(db.Integer, db.ForeignKey('board.id'), nullable=False)
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(150), nullable=False)
    url = db.Column(db.String(500), nullable=False)
    x = db.Column(db.Integer, nullable=False)
    y = db.Column(db.Integer, nullable=False)
    width = db.Column(db.Integer, nullable=False)
    height = db.Column(db.Integer, nullable=False)


@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))


@app.route("/register", methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        email = form.email.data.lower()
        hashed_password = generate_password_hash(form.password.data, method='pbkdf2:sha256', salt_length=16)
        user = User(username=form.username.data, email=email, password=hashed_password)
        db.session.add(user)
        db.session.commit()
        flash('Your account has been created!', 'success')
        return redirect(url_for('login'))
    return render_template('register.html', form=form)


@app.route("/login", methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        email = form.email.data.lower()
        user = User.query.filter_by(email=email).first()
        if user and check_password_hash(user.password, form.password.data):
            login_user(user, remember=form.remember.data)
            return redirect(url_for('boards'))
        else:
            flash('Login Unsuccessful. Please check email and password', 'danger')
    return render_template('login.html', form=form)


@app.route("/logout")
@login_required
def logout():
    logout_user()
    return redirect(url_for('login'))


@app.route('/')
@login_required
def boards():
    user_boards = Board.query.filter_by(user_id=current_user.id).all()
    public_boards = Board.query.filter_by(is_public=True).all()
    return render_template('boards.html', user_boards=user_boards, public_boards=public_boards)


@app.route('/create_board', methods=['GET', 'POST'])
@login_required
def create_board():
    if request.method == 'POST':
        board_name = request.form.get('board_name')
        is_public = 'is_public' in request.form
        new_board = Board(name=board_name, is_public=is_public, owner=current_user)
        db.session.add(new_board)
        db.session.commit()
        return redirect(url_for('boards'))
    return render_template('create_board.html')


@app.route('/board/<int:board_id>')
@login_required
def board_view(board_id):
    board = Board.query.get_or_404(board_id)
    if board.is_public or board.owner == current_user:
        notes = Note.query.filter_by(board_id=board_id)
        images = Image.query.filter_by(board_id=board_id)
        audios = Audio.query.filter_by(board_id=board_id)
        links = Link.query.filter_by(board_id=board_id).all()
        iframes = Iframe.query.filter_by(board_id=board_id).all()
        return render_template('index.html',
                               board=board,
                               notes=notes,
                               images=images,
                               audios=audios,
                               links=links,
                               iframes=iframes)
    else:
        flash('Access denied', 'danger')
        return redirect(url_for('boards'))


@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        return jsonify({'error': 'No file part'}), 400

    file = request.files['file']
    if file.filename == '':
        return jsonify({'error': 'No selected file'}), 400

    # Open the image file
    image = PILImage.open(file.stream)

    if image.mode != 'RGB':
        image = image.convert('RGB')

    unique_filename = f"{uuid.uuid4().hex}"
    filepath = os.path.join(app.config['UPLOAD_IMAGE_FOLDER'], unique_filename)
    os.makedirs(app.config['UPLOAD_IMAGE_FOLDER'], exist_ok=True)

    image.save(filepath, "JPEG", quality=100)

    return jsonify({'url': os.path.join('/', app.config['UPLOAD_IMAGE_FOLDER'], unique_filename)})


@app.route('/fetch_image_from_url', methods=['POST'])
def fetch_image_from_url():
    data = request.get_json()
    image_url = data.get('url')
    if not image_url:
        return jsonify({'error': 'No URL provided'}), 400

    try:
        # Скачивание изображения с переданного URL
        response = requests.get(image_url)
        response.raise_for_status()

        # Генерация уникального имени файла
        filename = f"{uuid.uuid4().hex}"
        filepath = os.path.join(app.config['UPLOAD_IMAGE_FOLDER'], filename)
        os.makedirs(app.config['UPLOAD_IMAGE_FOLDER'], exist_ok=True)

        # Сохранение изображения в jpg формате
        image = PILImage.open(io.BytesIO(response.content))
        if image.mode != 'RGB':
            image = image.convert('RGB')
        image.save(filepath, "JPEG", quality=100)

        # Возвращаем путь к сохраненному изображению
        return jsonify({'url': os.path.join('/', app.config['UPLOAD_IMAGE_FOLDER'], filename)})

    except requests.RequestException as e:
        return jsonify({'error': str(e)}), 500


@app.route('/upload_audio', methods=['POST'])
def upload_audio():
    if 'file' not in request.files:
        return jsonify({'error': 'No file part'}), 400

    file = request.files['file']
    if file.filename == '':
        return jsonify({'error': 'No selected file'}), 400

    filename = file.filename
    filepath = os.path.join(app.config['UPLOAD_AUDIO_FOLDER'], filename)
    os.makedirs(app.config['UPLOAD_AUDIO_FOLDER'], exist_ok=True)
    file.save(filepath)

    return jsonify({'url': os.path.join('/', app.config['UPLOAD_AUDIO_FOLDER'], filename), 'file_name': filename})


@socketio.on('create_note')
def handle_create_note(data):
    text = data['text']
    x = data['x']
    y = data['y']
    width = data['width']
    height = data['height']
    board_id = data.get('board_id')

    if not board_id:
        emit('error', {'error': 'Board ID is missing'}, broadcast=False)
        return

    new_note = Note(text=text, x=x, y=y, width=width, height=height, board_id=board_id)
    db.session.add(new_note)
    db.session.commit()
    emit('new_note', {'id': new_note.id, 'text': text, 'x': x, 'y': y, 'width': width, 'height': height},
         broadcast=True)


@socketio.on('update_note')
def handle_update_note(data):
    note = db.session.get(Note, data['id'])
    if note:
        note.text = data['text']
        note.x = data['x']
        note.y = data['y']
        note.width = data['width']
        note.height = data['height']
        db.session.commit()
        emit('updated_note',
             {'id': note.id, 'text': note.text, 'x': note.x, 'y': note.y, 'width': note.width, 'height': note.height},
             broadcast=True)


@socketio.on('create_image')
def handle_create_image(data):
    url = data.get('url')
    x = data.get('x')
    y = data.get('y')
    width = data.get('width')
    height = data.get('height')
    board_id = data.get('board_id')

    if not board_id:
        emit('error', {'error': 'Board ID is missing'}, broadcast=False)
        return

    if not url or not x or not y or not width or not height:
        emit('error', {'error': 'Missing required data'})
        return

    parsed_url = urlparse(url)
    normalized_url = parsed_url.path

    new_image = Image(url=normalized_url, x=x, y=y, width=width, height=height, board_id=board_id)
    db.session.add(new_image)
    db.session.commit()
    emit('new_image', {'id': new_image.id, 'url': normalized_url, 'x': x, 'y': y, 'width': width, 'height': height},
         broadcast=True)


@socketio.on('update_image')
def handle_update_image(data):
    image = db.session.get(Image, data['id'])
    if image:
        image.url = data.get('url', image.url)
        image.x = data.get('x', image.x)
        image.y = data.get('y', image.y)
        image.width = data.get('width', image.width)
        image.height = data.get('height', image.height)
        db.session.commit()
        emit('updated_image', {'id': image.id, 'url': image.url, 'x': image.x, 'y': image.y, 'width': image.width,
                               'height': image.height}, broadcast=True)


@socketio.on('delete_element')
def handle_delete_element(data):
    element_id = data['id']
    element_type = data.get('type')

    if element_type == 'note':
        element = db.session.get(Note, element_id)
    elif element_type == 'image':
        element = db.session.get(Image, element_id)
    elif element_type == 'link':
        element = db.session.get(Link, element_id)
    elif element_type == 'iframe':
        element = db.session.get(Iframe, element_id)
    else:
        emit('error', {'error': 'Invalid element type'})
        return

    if element:
        if element_type == 'image':
            image_path = os.path.join(app.config['UPLOAD_IMAGE_FOLDER'], os.path.basename(element.url))
            image_vr_path = os.path.join(app.config['UPLOAD_IMAGE_VR_FOLDER'], os.path.basename(element.url))
            if os.path.exists(image_path):
                os.remove(image_path)

            if os.path.exists(image_vr_path):
                os.remove(image_vr_path)

        db.session.delete(element)
        db.session.commit()
        emit('element_deleted', {'id': element_id, 'type': element_type}, broadcast=True)
    else:
        emit('error', {'error': 'Element not found'})


@socketio.on('delete_audio')
def handle_delete_audio(data):
    audio = db.session.get(Audio, data['id'])
    if audio:
        audio_path = os.path.join(app.config['UPLOAD_AUDIO_FOLDER'], os.path.basename(audio.url))
        if os.path.exists(audio_path):
            os.remove(audio_path)
        db.session.delete(audio)
        db.session.commit()
        emit('audio_deleted', {'id': audio.id}, broadcast=True)


@socketio.on('create_audio')
def handle_create_audio(data):
    file_name = data['file_name']
    url = data['url']
    x = data['x']
    y = data['y']
    width = data['width']
    height = data['height']
    board_id = data.get('board_id')

    if not board_id:
        emit('error', {'error': 'Board ID is missing'}, broadcast=False)
        return

    parsed_url = urlparse(url)
    normalized_url = parsed_url.path

    new_audio = Audio(file_name=file_name, url=normalized_url, x=x, y=y, width=width, height=height, board_id=board_id)
    db.session.add(new_audio)
    db.session.commit()
    emit('new_audio',
         {'id': new_audio.id, 'file_name': file_name, 'url': normalized_url, 'x': x, 'y': y, 'width': width,
          'height': height},
         broadcast=True)


@socketio.on('update_audio')
def handle_update_audio(data):
    audio = db.session.get(Audio, data['id'])
    if audio:
        audio.file_name = data.get('file_name', audio.file_name)
        audio.url = data.get('url', audio.url)
        audio.x = data.get('x', audio.x)
        audio.y = data.get('y', audio.y)
        audio.width = data.get('width', audio.width)
        audio.height = data.get('height', audio.height)
        db.session.commit()
        emit('updated_audio',
             {'id': audio.id, 'file_name': audio.file_name, 'url': audio.url, 'x': audio.x, 'y': audio.y,
              'width': audio.width, 'height': audio.height}, broadcast=True)


@socketio.on('create_link')
def handle_create_link(data):
    name = data['name']
    url = data['url']
    x = data['x']
    y = data['y']
    width = data['width']
    height = data['height']
    board_id = data.get('board_id')

    if not board_id:
        emit('error', {'error': 'Board ID is missing'}, broadcast=False)
        return

    new_link = Link(name=name, url=url, x=x, y=y, width=width, height=height, board_id=board_id)
    db.session.add(new_link)
    db.session.commit()
    emit('new_link', {'id': new_link.id, 'name': name, 'url': url, 'x': x, 'y': y, 'width': width, 'height': height},
         broadcast=True)


@socketio.on('update_link')
def handle_update_link(data):
    link = db.session.get(Link, data['id'])
    if link:
        link.name = data['name']
        link.url = data['url']
        link.x = data['x']
        link.y = data['y']
        link.width = data['width']
        link.height = data['height']
        db.session.commit()
        emit('updated_link',
             {'id': link.id, 'name': link.name, 'url': link.url, 'x': link.x, 'y': link.y, 'width': link.width,
              'height': link.height}, broadcast=True)


@socketio.on('create_iframe')
def handle_create_iframe(data):
    name = data.get('name', 'Iframe')
    url = data['url']
    x = data['x']
    y = data['y']
    width = data['width']
    height = data['height']
    board_id = data.get('board_id')

    if not board_id:
        emit('error', {'error': 'Board ID is missing'}, broadcast=False)
        return

    new_iframe = Iframe(name=name, url=url, x=x, y=y, width=width,
                        height=height, board_id=board_id)
    db.session.add(new_iframe)
    db.session.commit()
    emit('new_iframe', {'id': new_iframe.id, 'name': name, 'url': url, 'x': x, 'y': y,
                        'width': width, 'height': height}, broadcast=True)


@socketio.on('update_iframe')
def handle_update_iframe(data):
    iframe = db.session.get(Iframe, data['id'])
    if iframe:
        iframe.name = data.get('name', iframe.name)
        iframe.url = data.get('url', iframe.url)
        iframe.x = data.get('x', iframe.x)
        iframe.y = data.get('y', iframe.y)
        iframe.width = data.get('width', iframe.width)
        iframe.height = data.get('height', iframe.height)
        db.session.commit()
        emit('updated_iframe', {'id': iframe.id, 'name': iframe.name, 'url': iframe.url,
                                'x': iframe.x, 'y': iframe.y, 'width': iframe.width, 'height': iframe.height},
             broadcast=True)


if __name__ == '__main__':
    with app.app_context():
        if not os.path.exists('data.db'):
            db.create_all()
    socketio.run(app, host='192.168.0.105', port=7846, debug=True, allow_unsafe_werkzeug=True)


Помоги разобраться почему у меня элемент iframe добавляется не по центру видимой области а в левом верхнем углу, при этом элемент аудио появляется по центру, в консоли ошибок нет