from flask import Flask, render_template, request, jsonify
from flask_socketio import SocketIO, emit
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import Session
import os

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

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


class Note(db.Model):
    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):
    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):
    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)


@app.route('/')
def index():
    notes = Note.query.all()
    images = Image.query.all()
    audios = Audio.query.all()
    return render_template('index.html', notes=notes, images=images, audios=audios)


@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

    filename = file.filename
    filepath = os.path.join(app.config['UPLOAD_IMAGE_FOLDER'], filename)
    os.makedirs(app.config['UPLOAD_IMAGE_FOLDER'], exist_ok=True)  # Ensure the upload folder exists
    file.save(filepath)

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


@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']
    new_note = Note(text=text, x=x, y=y, width=width, height=height)
    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')

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

    new_image = Image(url=url, x=x, y=y, width=width, height=height)
    db.session.add(new_image)
    db.session.commit()
    emit('new_image', {'id': new_image.id, 'url': 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)
    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))
            if os.path.exists(image_path):
                os.remove(image_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('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']

    new_audio = Audio(file_name=file_name, url=url, x=x, y=y, width=width, height=height)
    db.session.add(new_audio)
    db.session.commit()
    emit('new_audio',
         {'id': new_audio.id, 'file_name': file_name, 'url': 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('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)


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


// 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); // Смещаем board-right
        }

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

// 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) {
            imageData = {"url": url};
        } else if (fileInput.files.length > 0) {
            const file = fileInput.files[0];
            imageData = await uploadFile(file, '/upload');
        }

        if (imageData) {
            addImageToCenter(imageData);
            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();
    handleExistingAudioElements();
});

const moveStepVr = 10;  // Шаг для перемещения
const zoomStepVr = 0.01;  // Шаг для увеличения-уменьшения
const MIN_SCALEVr = 0.05; // Минимальный масштаб
const MAX_SCALEVr = 5;   // Максимальный масштаб
const DEAD_ZONE = 0.1; // Мертвая зона для стиков

let translateVrXLeft = 0;
let translateVrYLeft = 0;
let scaleVrLeft = 0.05; // Начальный масштаб для левой части

let translateVrXRight = 0;
let translateVrYRight = 0;
let scaleVrRight = 0.05; // Начальный масштаб для правой части

function getInitialTransformValues(board) {
    const transform = board.style.transform || '';
    const translateMatch = transform.match(/translate\((-?\d*\.?\d*)px,\s*(-?\d*\.?\d*)px\)/);
    const scaleMatch = transform.match(/scale\((-?\d*\.?\d*)\)/);

    const translateX = translateMatch ? parseFloat(translateMatch[1]) : 0;
    const translateY = translateMatch ? parseFloat(translateMatch[2]) : 0;
    const scale = scaleMatch ? parseFloat(scaleMatch[1]) : 0.05;

    return { translateX, translateY, scale };
}

window.addEventListener('gamepadconnected', function(e) {
    console.log('Gamepad connected at index %d: %s. %d buttons, %d axes.',
        e.gamepad.index, e.gamepad.id,
        e.gamepad.buttons.length, e.gamepad.axes.length);
    requestAnimationFrame(handleGamepadInput);
});

function handleGamepadInput() {
    const gamepads = navigator.getGamepads();
    if (!gamepads) return;

    const gamepad = gamepads[0];
    if (!gamepad) return;

    const boardLeft = document.getElementById('board-left');
    const boardRight = document.getElementById('board-right');

    if (boardLeft && boardRight) {
        if (translateVrXLeft === 0 && translateVrYLeft === 0 && scaleVrLeft === 0.05) {
            const initialLeft = getInitialTransformValues(boardLeft);
            translateVrXLeft = initialLeft.translateX;
            translateVrYLeft = initialLeft.translateY;
            scaleVrLeft = initialLeft.scale;
        }

        if (translateVrXRight === 0 && translateVrYRight === 0 && scaleVrRight === 0.05) {
            const initialRight = getInitialTransformValues(boardRight);
            translateVrXRight = initialRight.translateX;
            translateVrYRight = initialRight.translateY;
            scaleVrRight = initialRight.scale;
        }

        const centerLeftX = (boardLeft.offsetWidth / 4);
        const centerLeftY = boardLeft.offsetHeight / 2;
        const centerRightX = (boardRight.offsetWidth /8);
        const centerRightY = boardRight.offsetHeight / 2;

        let newScaleLeft = scaleVrLeft;
        let newScaleRight = scaleVrRight;

        if (gamepad.buttons[5].pressed) { // Zoom out
            newScaleLeft = Math.max(MIN_SCALEVr, scaleVrLeft - zoomStepVr);
            newScaleRight = Math.max(MIN_SCALEVr, scaleVrRight - zoomStepVr);
        }
        if (gamepad.buttons[3].pressed) {  // Zoom in
            newScaleLeft = Math.min(MAX_SCALEVr, scaleVrLeft + zoomStepVr);
            newScaleRight = Math.min(MAX_SCALEVr, scaleVrRight + zoomStepVr);
        }

        // Изменение translateX и translateY, чтобы центр оставался на месте
        const zoomFactorLeft = newScaleLeft / scaleVrLeft;
        const zoomFactorRight = newScaleRight / scaleVrRight;

        translateVrXLeft = centerLeftX - (centerLeftX - translateVrXLeft) * zoomFactorLeft;
        translateVrYLeft = centerLeftY - (centerLeftY - translateVrYLeft) * zoomFactorLeft;

        translateVrXRight = centerRightX - (centerRightX - translateVrXRight) * zoomFactorRight;
        translateVrYRight = centerRightY - (centerRightY - translateVrYRight) * zoomFactorRight;

        scaleVrLeft = newScaleLeft;
        scaleVrRight = newScaleRight;

        // Проверка осей стиков для перемещения с учетом мертвой зоны
        const moveLeftX = Math.abs(gamepad.axes[0]) > DEAD_ZONE ? gamepad.axes[0] * moveStepVr : 0;
        const moveLeftY = Math.abs(gamepad.axes[1]) > DEAD_ZONE ? gamepad.axes[1] * moveStepVr : 0;
        const moveRightX = Math.abs(gamepad.axes[0]) > DEAD_ZONE ? gamepad.axes[0] * moveStepVr : 0;
        const moveRightY = Math.abs(gamepad.axes[1]) > DEAD_ZONE ? gamepad.axes[1] * moveStepVr : 0;

        translateVrXLeft -= moveLeftX;
        translateVrYLeft -= moveLeftY;

        if (gamepad.buttons[9].pressed) {
            translateVrXRight -= 1;
        }
        if (gamepad.buttons[2].pressed) {
            translateVrXRight += 1;
        }

        translateVrXRight -= moveRightX;
        translateVrYRight -= moveRightY;

        updateVRTransform(boardLeft, translateVrXLeft, translateVrYLeft, scaleVrLeft);
        updateVRTransform(boardRight, translateVrXRight, translateVrYRight, scaleVrRight);
    }

    requestAnimationFrame(handleGamepadInput);
}

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


Как мне сделать чтобы для правой доски загружались изображения из папки uploads/images/vr, а для левой как и раньше uploads/images. Имена файлов будут одинаковые. Если для правой доски изображение не будет найдено, то как и раньше просто берем одинаковые изображения для обоих досок