cvat_shortcuts

CVAT Shorcuts

// ==UserScript==
// @name         cvat_shortcuts
// @namespace    http://tampermonkey.net/
// @version      1.08.2
// @description  CVAT Shorcuts
// @author       Megumin
// @match        http*://cvat:8080/*
// @match        http*://cvat-backup:8080/*
// @match        http://cvat.lastpassenger.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=honeycomb.vision
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    const handleUrlChange = () => {
        if (window.location.href.includes("/tasks/") && window.location.href.includes("/jobs/")) {

            var observer

            function waitForElement(selector, callback) {
                const observer = new MutationObserver(() => {
                    const element = document.querySelector(selector);
                    if (element) {
                        observer.disconnect(); // Зупиняємо спостереження після знаходження елемента
                        callback(element); // Викликаємо функцію з елементом
                    }
                });

                // Налаштування для спостереження за змінами у DOM
                observer.observe(document.body, {
                    childList: true,
                    subtree: true,
                });
            }

            function opacityToZero() {
                const slider = document.querySelector('.cvat-appearance-opacity-slider');
                const sliderHandle = slider.querySelector('.ant-slider-handle');
                sliderHandle.style.left = 0;

                // Створити подію миші (drag/drop)
                const mouseDownEvent = new MouseEvent('mousedown', {
                    bubbles: true,
                    cancelable: true,
                    clientX: sliderHandle.getBoundingClientRect().left,
                    clientY: sliderHandle.getBoundingClientRect().top,
                });

                const mouseMoveEvent = new MouseEvent('mousemove', {
                    bubbles: true,
                    cancelable: true,
                    clientX: sliderHandle.getBoundingClientRect().left - 100, // Змістити на 100 пікселів
                    clientY: sliderHandle.getBoundingClientRect().top,
                });

                const mouseUpEvent = new MouseEvent('mouseup', {
                    bubbles: true,
                    cancelable: true,
                    clientX: sliderHandle.getBoundingClientRect().left - 100,
                    clientY: sliderHandle.getBoundingClientRect().top,
                });

                // Відправити події
                sliderHandle.dispatchEvent(mouseDownEvent);
                sliderHandle.dispatchEvent(mouseMoveEvent);
                sliderHandle.dispatchEvent(mouseUpEvent);

                console.log(`Повзунок переміщено на 0`);

                const slider2 = document.querySelector('.cvat-appearance-selected-opacity-slider');
                const sliderHandle2 = slider2.querySelector('.ant-slider-handle');
                sliderHandle2.style.left = 0;

                // Створити подію миші (drag/drop)
                const mouseDownEvent2 = new MouseEvent('mousedown', {
                    bubbles: true,
                    cancelable: true,
                    clientX: sliderHandle2.getBoundingClientRect().left,
                    clientY: sliderHandle2.getBoundingClientRect().top,
                });

                const mouseMoveEvent2 = new MouseEvent('mousemove', {
                    bubbles: true,
                    cancelable: true,
                    clientX: sliderHandle2.getBoundingClientRect().left - 100, // Змістити на 100 пікселів
                    clientY: sliderHandle2.getBoundingClientRect().top,
                });

                const mouseUpEvent2 = new MouseEvent('mouseup', {
                    bubbles: true,
                    cancelable: true,
                    clientX: sliderHandle2.getBoundingClientRect().left - 100,
                    clientY: sliderHandle2.getBoundingClientRect().top,
                });

                // Відправити події
                sliderHandle2.dispatchEvent(mouseDownEvent2);
                sliderHandle2.dispatchEvent(mouseMoveEvent2);
                sliderHandle2.dispatchEvent(mouseUpEvent2);

                console.log(`Повзунок переміщено на 0`);
            }

            // Initialize
            (function () {
                console.log("Виконуюсь перед завантаженням")
                waitForElement('[data-node-key="issues"]', (element) => {
                    console.log('Елемент знайдено:', element);
                    // Виконуємо певну дію
                    element.click();
                    opacityToZero();

                    const objects = document.querySelector('[data-node-key="objects"]');
                    setTimeout(() => {
                        objects.click();
                        console.log('Клік виконано');
                    }, 50);

                });

                let size_monitoring = "off"; // off | small10 | small4
                // Увімкнення size monitoring автоматично при відкритті нового job
                size_monitoring = "small10"; // або "small4", якщо хочеш інший за замовчуванням

                const autoMessage = document.createElement("span");
                autoMessage.id = "small-shape-message";
                autoMessage.textContent = "Size monitoring: Enabled (auto)";
                autoMessage.style.position = "fixed";
                autoMessage.style.left = "10px";
                autoMessage.style.top = "10px";
                autoMessage.style.backgroundColor = "rgba(0, 0, 0, 0.7)";
                autoMessage.style.color = "white";
                autoMessage.style.fontSize = "100px";
                autoMessage.style.zIndex = "1000";
                autoMessage.style.borderRadius = "15px";
                autoMessage.style.padding = "20px 30px";
                document.body.appendChild(autoMessage);

                setTimeout(() => {
                    autoMessage.remove();
                }, 1500);

                // === ІНДИКАТОР РЕЖИМУ SIZE MONITORING ===

                function updateSizeMonitoringIndicator() {
                    let indicator = document.getElementById("size-monitoring-indicator");

                    // Знаходимо кнопку Redo
                    const redoButton = document.querySelector('.cvat-annotation-header-redo-button');
                    if (!redoButton) {
                        setTimeout(updateSizeMonitoringIndicator, 500);
                        return;
                    }

                    // Створюємо індикатор, якщо його ще немає
                    if (!indicator) {
                        indicator = document.createElement("div");
                        indicator.id = "size-monitoring-indicator";
                        indicator.style.position = "absolute";
                        indicator.style.fontSize = "14px";
                        indicator.style.fontWeight = "bold";
                        indicator.style.zIndex = "2000";
                        indicator.style.background = "rgba(0, 0, 0, 0.6)";
                        indicator.style.color = "white";
                        indicator.style.padding = "3px 8px";
                        indicator.style.borderRadius = "6px";
                        indicator.style.pointerEvents = "none";
                        document.body.appendChild(indicator);
                    }

                    // Визначаємо позицію кнопки Redo
                    const rect = redoButton.getBoundingClientRect();
                    indicator.style.top = `${rect.top + window.scrollY + 4}px`; // трішки нижче
                    indicator.style.left = `${rect.right + window.scrollX + 8}px`; // трохи правіше кнопки

                    // Змінюємо текст і колір відповідно до режиму
                    if (size_monitoring === "off") {
                        indicator.textContent = "OFF";
                        indicator.style.color = "#ccc";
                    } else if (size_monitoring === "small10") {
                        indicator.textContent = "10×10";
                        indicator.style.color = "red";
                    } else if (size_monitoring === "small4") {
                        indicator.textContent = "4×4";
                        indicator.style.color = "lime";
                    }

                    // Оновлюємо позицію при зміні розміру вікна
                    if (!window.__sizeMonitoringResizeHandler) {
                        window.__sizeMonitoringResizeHandler = () => {
                            const rect = redoButton.getBoundingClientRect();
                            indicator.style.top = `${rect.top + window.scrollY + 4}px`;
                            indicator.style.left = `${rect.right + window.scrollX + 8}px`;
                        };
                        window.addEventListener("resize", window.__sizeMonitoringResizeHandler);
                    }
                }


                // Викликати після кожного перемикання
                document.addEventListener("keydown", (event) => {
                    if (event.key === "F8") {
                        updateSizeMonitoringIndicator();
                    }
                });

                // Викликати при старті (щоб одразу показував поточний режим)
                updateSizeMonitoringIndicator();

                // Toggle monitoring mode (F8)
                const toggleMonitoring = (event) => {
                    if (event.key === "F8") {
                        if (size_monitoring === "off") {
                            size_monitoring = "small10";
                        } else if (size_monitoring === "small10") {
                            size_monitoring = "small4";
                        } else {
                            size_monitoring = "off";
                        }

                        updateSizeMonitoringIndicator();

                        const addedText = document.createElement("span");
                        addedText.id = "small-shape-message";
                        let modeText =
                            size_monitoring === "off"
                        ? "Disabled"
                        : size_monitoring === "small10"
                        ? "<10x10"
                        : "<4x4";

                        addedText.textContent = `Size monitoring: ${modeText}`;
                        addedText.style.position = "fixed";
                        addedText.style.left = "10px";
                        addedText.style.top = "10px";
                        addedText.style.backgroundColor = "rgba(0, 0, 0, 0.7)";
                        addedText.style.color = "white";
                        addedText.style.fontSize = "120px";
                        addedText.style.zIndex = "1000";
                        document.body.appendChild(addedText);

                        setTimeout(() => {
                            addedText.remove();
                        }, 1500);
                    }
                };

                document.addEventListener("keydown", toggleMonitoring);

                if (!window.location.href.includes("lastpassenger")) {
                    const observer = new MutationObserver(() => {
                        if (size_monitoring === "off") return;

                        const shapes = document.querySelectorAll(".cvat_canvas_shape:not(.cvat_canvas_hidden)");

                        let foundSmallShape = false;
                        let messageContent = "";

                        shapes.forEach((shape) => {
                            const currentWidth = Math.round(parseFloat(shape.getAttribute("width")) || 0);
                            const currentHeight = Math.round(parseFloat(shape.getAttribute("height")) || 0);

                            if (
                                (size_monitoring === "small10" && (currentWidth < 10 || currentHeight < 10)) ||
                                (size_monitoring === "small4" && (currentWidth < 4 || currentHeight < 4))
                            ) {
                                foundSmallShape = true;
                                messageContent = `${currentWidth} x ${currentHeight} --- RBA!!!`;
                            }
                        });

                        if (foundSmallShape) {
                            // Перевіряємо, чи вже є повідомлення на екрані
                            let existingMessage = document.getElementById("small-shape-message");
                            if (!existingMessage) {
                                // Створюємо повідомлення
                                const addedText = document.createElement("span");
                                addedText.id = "small-shape-message";
                                addedText.textContent = messageContent;
                                addedText.style.position = "fixed";
                                addedText.style.left = "10px";
                                addedText.style.top = "10px";
                                addedText.style.backgroundColor = "rgba(0, 0, 0, 0.7)";
                                addedText.style.color = "red";
                                addedText.style.fontSize = "120px";
                                addedText.style.zIndex = "1000";
                                document.body.appendChild(addedText);

                                setTimeout(() => {
                                    addedText.remove();
                                }, 500);
                            }
                        }
                    });

                    observer.observe(document.body, { childList: true, subtree: true });
                }
            })();

            document.addEventListener("keydown", function(e) {
                // Open task
                if (e.ctrlKey && e.shiftKey && e.code === "KeyS") {
                    e.preventDefault();

                    const currentUrl = window.location.href;
                    const newUrl = currentUrl.split('/jobs')[0];
                    const job_id = currentUrl.split('/jobs/')[1];
                    console.log(job_id);
                    localStorage.setItem('job_id', job_id);
                    window.location.href = newUrl;
                }

                // Change mode Standart and Review
                if (e.altKey && e.code === "KeyW") {
                    e.preventDefault();
                    const selectorDiv = document.querySelector(".ant-select-selector");
                    if (selectorDiv) {
                        const mouseDownEvent = new MouseEvent('mousedown', { bubbles: true });
                        selectorDiv.dispatchEvent(mouseDownEvent);

                        setTimeout(() => {
                            const currentOption = selectorDiv.innerText.trim();
                            const targetOptionText = currentOption === "Review" ? "Standard" : "Review";
                            const targetOption = Array.from(document.querySelectorAll(".ant-select-item-option")).find(option => option.innerText.trim() === targetOptionText);

                            setTimeout(() => {
                                const issues = document.querySelector('[data-node-key="issues"]');
                                issues.click();
                                setTimeout(() => {
                                    const objects = document.querySelector('[data-node-key="objects"]');
                                    objects.click();
                                    console.log('Клік виконано');
                                }, 50);
                            }, 50);
                            targetOption.click();
                        }, 50);
                    }
                }

                // INFO
                if (e.key === "F3") {
                    e.preventDefault();
                    const button = document.querySelector(".cvat-annotation-header-info-button");
                    const panel = document.querySelector(".ant-modal-mask");

                    if (panel) {
                        const ok = Array.from(document.querySelectorAll("button"))
                        .find(btn => btn.innerText.trim() === "OK");
                        ok.click();
                    } else {
                        button.click()
                    }
                }

                // COPY OPF
                if (e.key === "F4") {
                    const button = document.querySelector(".cvat-annotation-header-info-button");
                    button.click()

                    setTimeout(() => {
                        const tableBody = document.querySelector(".ant-table-tbody");

                        const cells = tableBody.querySelectorAll(".ant-table-cell.ant-table-cell-fix-right");
                        const lastCell = cells[cells.length - 1];
                        const copyToClipboard = (text) => {
                            const textarea = document.createElement('textarea');
                            textarea.value = text;
                            document.body.appendChild(textarea);
                            textarea.select();
                            try {
                                document.execCommand('copy');
                                console.log('Значення скопійовано в буфер обміну:', text);
                            } catch (err) {
                                console.error('Не вдалося скопіювати текст в буфер обміну:', err);
                            }
                            document.body.removeChild(textarea);
                        };

                        const cellValue = lastCell.innerText;
                        copyToClipboard(cellValue);
                        const ok = Array.from(document.querySelectorAll("button"))
                        .find(btn => btn.innerText.trim() === "OK");
                        ok.click();
                    }, 400);
                }

                // DESTROYED
                if (e.altKey && e.code === "KeyD") {
                    e.preventDefault();
                    const active_shape = document.querySelector(".cvat-objects-sidebar-state-active-item");
                    const flag = active_shape.querySelector(".ant-collapse-item-active");

                    if (!flag) {
                        const collapse = active_shape.querySelector(".ant-collapse-header");
                        const mouseDownEvent = new MouseEvent('mousedown', { bubbles: true });
                        collapse.click();
                        setTimeout(() => {
                            const destroyed = active_shape.querySelector(".ant-checkbox-input");
                            destroyed.click();
                        }, 100);
                    } else {
                        const destroyed = active_shape.querySelector(".ant-checkbox-input");
                        destroyed.click();
                    }

                }
                // Toggle "Outlined borders" (Alt + F)
                if (e.altKey && e.code === "KeyF") {
                    e.preventDefault();
                    const outlinedBorders = Array.from(
                        document.querySelectorAll('.cvat-objects-appearance-content .ant-checkbox-wrapper')
                    ).find(el => el.textContent.includes('Outlined borders'));

                    if (outlinedBorders) {
                        outlinedBorders.click();
                        console.log("Outlined borders toggled");
                    } else {
                        console.log("Outlined borders checkbox not found");
                    }
                }
                // Cycle between "Label" → "Instance" → "Group" (Alt + I)
                if (e.altKey && e.code === "KeyI") {
                    e.preventDefault();
                    const radios = Array.from(document.querySelectorAll('.ant-radio-button-wrapper'));
                    const labelRadio = radios.find(el => el.textContent.trim() === 'Label');
                    const instanceRadio = radios.find(el => el.textContent.trim() === 'Instance');
                    const groupRadio = radios.find(el => el.textContent.trim() === 'Group');

                    if (!labelRadio || !instanceRadio || !groupRadio) {
                        console.log("One or more radio buttons not found");
                        return;
                    }

                    // Знаходимо, який зараз активний
                    const isLabelActive = labelRadio.classList.contains('ant-radio-button-wrapper-checked');
                    const isInstanceActive = instanceRadio.classList.contains('ant-radio-button-wrapper-checked');
                    const isGroupActive = groupRadio.classList.contains('ant-radio-button-wrapper-checked');

                    // Циклічне перемикання
                    if (isLabelActive) {
                        instanceRadio.click();
                        console.log("Switched to Instance");
                    } else if (isInstanceActive) {
                        groupRadio.click();
                        console.log("Switched to Group");
                    } else if (isGroupActive) {
                        labelRadio.click();
                        console.log("Switched to Label");
                    } else {
                        // Якщо жоден не вибраний — перейти до Label за замовчуванням
                        labelRadio.click();
                        console.log("Switched to Label (default)");
                    }
                }
                // Box Size
                if (e.altKey && e.code === "KeyS") {
                    e.preventDefault();

                    const shapes = document.querySelectorAll(".cvat_canvas_shape:not(.cvat_canvas_hidden)");

                    shapes.forEach((shape) => {
                        // Отримуємо розміри shape
                        const currentWidth = Math.round(parseFloat(shape.getAttribute("width")) || 0);
                        const currentHeight = Math.round(parseFloat(shape.getAttribute("height")) || 0);

                        // Створюємо текстовий елемент
                        const addedText = document.createElement("span");
                        addedText.textContent = ` ${currentWidth} x ${currentHeight}`;
                        addedText.style.position = "absolute"; // Адаптуйте залежно від ваших стилів
                        addedText.style.left = `${shape.getBoundingClientRect().left + window.scrollX}px`;
                        addedText.style.top = `${shape.getBoundingClientRect().top + window.scrollY}px`;
                        addedText.style.backgroundColor = "rgba(0, 0, 0, 0.7)";
                        addedText.style.color = "white";
                        addedText.style.fontSize = "22px";
                        if (currentWidth < 10 || currentHeight < 10) {
                            addedText.style.color = "red";
                            addedText.style.fontSize = "32px";
                            addedText.textContent += ' RBA';
                        }

                        // Додаємо текст до документа
                        document.body.appendChild(addedText);

                        // Видаляємо текст через 2 секунди
                        setTimeout(() => {
                            addedText.remove();
                        }, 500);
                    });
                }

                // Keyframe on all boxes
                if (e.altKey && e.code === "KeyA") {
                    e.preventDefault();
                    const icons = document.querySelectorAll(".cvat-object-item-button-keyframe:not(.cvat-object-item-button-keyframe-enabled)");
                    console.log("fine")
                    icons.forEach(icon => {
                        icon.click();
                        console.log("click")
                    });
                }

                // Close all boxes
                if (e.code === "KeyX" && e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA") {
                    e.preventDefault();
                    const icons = document.querySelectorAll(".anticon-select");

                    icons.forEach(icon => {
                        icon.click();
                    });
                }

                // Open all boxes
                if (e.code === "KeyZ" && !e.ctrlKey && e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA") {
                    e.preventDefault();
                    const icons = document.querySelectorAll(".cvat-object-item-button-outside-enabled");

                    icons.forEach(icon => {
                        icon.click();
                    });
                }

                // Next issues
                if ((e.code === "Period" && e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA") || (e.altKey && e.code === "KeyE" && e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA")) {
                    e.preventDefault();
                    const next_issues = document.querySelector(".cvat-issues-sidebar-next-frame");
                    next_issues.click();
                }

                // Previous issues
                if ((e.code === "Comma" && e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA") || (e.altKey && e.code === "KeyQ" && e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA")) {
                    e.preventDefault();
                    const previous_issues = document.querySelector(".cvat-issues-sidebar-previous-frame");
                    previous_issues.click();
                }

                // Hide/Unhide issues
                if ((e.code === "Slash" && e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA") || (e.altKey && e.code === "KeyR" && e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA")) {
                    e.preventDefault();

                    const hide_button = document.querySelector(".cvat-issues-sidebar-shown-issues");
                    const unhide_button = document.querySelector(".cvat-issues-sidebar-hidden-issues");
                    if (hide_button) {
                        hide_button.click();
                    } else {
                        unhide_button.click();
                    }
                }
            });

        } else if (window.location.href.includes("/tasks/") && !window.location.href.includes("/jobs/")) {

            document.addEventListener("keydown", function(e) {
                // Task status: completed || rejected
                if ((e.altKey && e.code === "KeyS") || (e.altKey && e.code === "KeyA")) {
                    const stage_text = 'acceptance'
                    var state_text = 'completed';
                    if (e.altKey && e.code === "KeyA") {
                        state_text = 'rejected';
                    }
                    e.preventDefault();
                    var stage = document.querySelector(".cvat-job-item-stage");
                    var state = document.querySelector(".cvat-job-item-state");

                    const job_id = localStorage.getItem('job_id');
                    console.log(job_id);
                    if (job_id != 0) {
                        console.log(job_id);
                        const job = document.querySelector(`div[data-row-id="${job_id}"]`);
                        stage = job.querySelector(".cvat-job-item-stage");
                        state = job.querySelector(".cvat-job-item-state");
                    }

                    const stageSelector = stage.querySelector(".ant-select-selector");
                    if (stageSelector) {
                        const mouseDownEvent = new MouseEvent('mousedown', { bubbles: true });
                        stageSelector.dispatchEvent(mouseDownEvent);

                        setTimeout(() => {
                            const targetOption = Array.from(document.querySelectorAll(".ant-select-item-option")).find(option => option.innerText.trim() === stage_text);
                            targetOption.click();
                        }, 50);
                    }

                    setTimeout(() => {
                        const stateSelector = state.querySelector(".ant-select-selector");
                        if (stateSelector) {
                            const mouseDownEvent = new MouseEvent('mousedown', { bubbles: true });
                            stateSelector.dispatchEvent(mouseDownEvent);

                            setTimeout(() => {
                                const targetOption = Array.from(document.querySelectorAll(".ant-select-item-option")).find(option => option.innerText.trim() === state_text);
                                targetOption.click();
                            }, 50);

                        }
                    }, 800);
                }
            });
        }
    };

    let lastUrl = window.location.href;
    setInterval(() => {
        const currentUrl = window.location.href;
        if (currentUrl !== lastUrl) {
            lastUrl = currentUrl;
            handleUrlChange();
        }
    }, 1000);

    handleUrlChange();
})();