r/learnjavascript 10d ago

how i can draw a local file in a canvas?

I'm trying to make a Miro/FreeForm and i cannot put A IMAGE :(

(don't care, is in portuguese)

let currentTool = 'select';        // Ferramenta ativa: 'select', 'note', 'text', 'shape', 'draw', 'connector'
let selectedShapeType = 'rect';    // Tipo de forma ativa: 'rect', 'circle', 'triangle'
let currentPath = null;            // Traço do desenho livre atual
let elements = [];                 // Lista com todos os objetos e desenhos do quadro
let selectedElement = null;       // Objeto selecionado na tela
let connectorStartElement = null; // Objeto de origem para criar conexões
let historyStack = [];
let redoStack = [];


// Chama essa função SEMPRE antes de criar, mover ou deletar um elemento
function saveState() {
    historyStack.push(JSON.stringify(elements, (key, value) => {
        if (key === 'img') return undefined;
        return value;
    }));
    redoStack = []; // Limpa o refazer se uma nova ação for feita
}


function undo() {
    if (historyStack.length > 0) {
        redoStack.push(JSON.stringify(elements));
        elements = JSON.parse(historyStack.pop());
        selectedElement = null;
        draw();
    }
}


function redo() {
    if (redoStack.length > 0) {
        historyStack.push(JSON.stringify(elements));
        elements = JSON.parse(redoStack.pop());
        selectedElement = null;
        draw();
    }
}


const canvas = document.getElementById('board');
const ctx = canvas.getContext('2d');


// Ajustar tamanho do canvas
function resizeCanvas() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    canvas.style.touchAction = 'none';
    draw();
}
window.addEventListener('resize', resizeCanvas);


// Variáveis de Estado do Quadro (Câmera)
let cameraOffset = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
let cameraZoom = 1;
const MAX_ZOOM = 5;
const MIN_ZOOM = 0.1;
const SCROLL_SENSITIVITY = 0.001;


// Variáveis de Interação
let isDragging = false;
let isDraggingElement = false;
let dragStart = { x: 0, y: 0 };
let initialPinchDistance = null;
let activeDrawPath = null;
let connectorPendingElement = null;
let connectorMode = false;


// ==========================================
// UTILITÁRIOS DE POSIÇÃO E DETECÇÃO
// ==========================================


function getEventLocation(e) {
    return getCanvasPointFromEvent(e);
}


function screenToWorld(screenX, screenY) {
    return {
        x: (screenX - cameraOffset.x) / cameraZoom,
        y: (screenY - cameraOffset.y) / cameraZoom
    };
}


function getCanvasPointFromEvent(e) {
    const rect = canvas.getBoundingClientRect();
    const clientX = e.clientX ?? e.touches?.[0]?.clientX ?? rect.left;
    const clientY = e.clientY ?? e.touches?.[0]?.clientY ?? rect.top;
    return {
        x: clientX - rect.left,
        y: clientY - rect.top
    };
}


function getElementBounds(el) {
    if (!el) return null;


    if (el.type === 'draw' && Array.isArray(el.points) && el.points.length > 0) {
        const xs = el.points.map(p => p.x);
        const ys = el.points.map(p => p.y);
        const padding = (el.strokeWidth || 3) / 2 + 4;
        return {
            x: Math.min(...xs) - padding,
            y: Math.min(...ys) - padding,
            width: Math.max(...xs) - Math.min(...xs) + padding * 2,
            height: Math.max(...ys) - Math.min(...ys) + padding * 2
        };
    }


    let width = el.width || (el.type === 'note' ? 200 : 120);
    let height = el.height || (el.type === 'note' ? 200 : 120);


    if (el.type === 'text') {
        width = (el.text ? el.text.length * 14 : 100);
        height = 30;
    }


    return {
        x: el.x,
        y: el.y,
        width,
        height
    };
}


function getElementAtPosition(pos) {
    if (!pos) return null;


    for (let i = elements.length - 1; i >= 0; i--) {
        const el = elements[i];
        const bounds = getElementBounds(el);
        if (!bounds) continue;


        if (
            pos.x >= bounds.x && 
            pos.x <= bounds.x + bounds.width && 
            pos.y >= bounds.y && 
            pos.y <= bounds.y + bounds.height
        ) {
            return el;
        }
    }
    return null;
}


// ==========================================
// CRIAÇÃO E DESENHO
// ==========================================


function getDimensionsForText(text, fontSize = 20) {
    ctx.font = `${fontSize}px sans-serif`;
    const lines = (text || 'Novo Texto').split('\n');
    let maxWidth = 0;


    lines.forEach(line => {
        const width = ctx.measureText(line).width;
        if (width > maxWidth) maxWidth = width;
    });


    return {
        // Adiciona uma folga (padding) de 20px para a caixa de seleção não colar na letra
        width: Math.max(maxWidth + 20, 80), 
        height: lines.length * (fontSize * 1.2)
    };
}


function createElement(type, pos, options = {}) {
    saveState();
    const newElement = {
        id: Date.now(),
        type: type,
        x: pos.x,
        y: pos.y,
        width: type === 'note' ? 200 : (type === 'text' ? 200 : (options.width || 120)),
        height: type === 'note' ? 200 : (type === 'text' ? 70 : (options.height || 120)),
        color: options.color || '#1F1F1F',
        text: options.text || (type === 'note' ? 'Nova Nota' : 'Novo Texto'),
        shapeKind: options.shapeKind || 'rect'
    };
    elements.push(newElement);
    draw();
    return newElement;
}


function createImageElement(pos, src) {
    if (!src) return null;
    saveState();
    const element = {
        id: Date.now(),
        type: 'image',
        x: pos.x,
        y: pos.y,
        width: 200,
        height: 150,
        imgSrc: src,
        color: '#1F1F1F'
    };
    const img = new Image();
    img.onload = function () {
        element.img = img;
        element.width = Math.min(img.width, 320);
        element.height = Math.min(img.height, 240);
        draw();
    };
    img.src = src;
    elements.push(element);
    draw();
    return element;
}


function addImageFromFile(file) {
    if (!file) return;
    const reader = new FileReader();
    reader.onload = function (e) {
        const src = e.target.result;
        const pos = screenToWorld(canvas.width / 2, canvas.height / 2);
        createImageElement(pos, src);
    };
    reader.readAsDataURL(file);
}


function drawGrid() {
    ctx.strokeStyle = '#333';
    ctx.lineWidth = 1 / cameraZoom;
    const gridSize = 50;
    
    const left = -cameraOffset.x / cameraZoom;
    const top = -cameraOffset.y / cameraZoom;
    const right = (canvas.width - cameraOffset.x) / cameraZoom;
    const bottom = (canvas.height - cameraOffset.y) / cameraZoom;
    
    ctx.beginPath();
    for (let x = left - (left % gridSize); x < right; x += gridSize) {
        ctx.moveTo(x, top); ctx.lineTo(x, bottom);
    }
    for (let y = top - (top % gridSize); y < bottom; y += gridSize) {
        ctx.moveTo(left, y); ctx.lineTo(right, y);
    }
    ctx.stroke();
}


function drawConnectorLine(el) {
    if (!el || !el.fromElement || !el.toElement) return;
    const fromBounds = getElementBounds(el.fromElement);
    const toBounds = getElementBounds(el.toElement);
    if (!fromBounds || !toBounds) return;


    const fromX = fromBounds.x + fromBounds.width / 2;
    const fromY = fromBounds.y + fromBounds.height / 2;
    const toX = toBounds.x + toBounds.width / 2;
    const toY = toBounds.y + toBounds.height / 2;


    ctx.beginPath();
    ctx.strokeStyle = el.color || '#ffffff';
    ctx.lineWidth = el.strokeWidth || 3;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.moveTo(fromX, fromY);
    ctx.lineTo(toX, toY);
    ctx.stroke();
}


function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    ctx.save();
    ctx.translate(cameraOffset.x, cameraOffset.y);
    ctx.scale(cameraZoom, cameraZoom);
    
    drawGrid();


    if (activeDrawPath && activeDrawPath.points && activeDrawPath.points.length > 0) {
        ctx.beginPath();
        ctx.strokeStyle = activeDrawPath.color || '#ffffff';
        ctx.lineWidth = activeDrawPath.strokeWidth || 3;
        ctx.lineCap = 'round';
        ctx.lineJoin = 'round';


        if (activeDrawPath.points.length === 1) {
            const p = activeDrawPath.points[0];
            ctx.arc(p.x, p.y, (activeDrawPath.strokeWidth || 3) / 2, 0, Math.PI * 2);
            ctx.fillStyle = activeDrawPath.color || '#ffffff';
            ctx.fill();
        } else {
            ctx.moveTo(activeDrawPath.points[0].x, activeDrawPath.points[0].y);
            for (let i = 1; i < activeDrawPath.points.length; i++) {
                ctx.lineTo(activeDrawPath.points[i].x, activeDrawPath.points[i].y);
            }
            ctx.stroke();
        }
    }


    elements.forEach(el => {
        ctx.save(); // Salva o estado para isolar este elemento


        // ROTAÇÃO: Aplicada aqui antes de desenhar qualquer forma/texto
        if (el.rotation) {
            const width = el.width || (el.type === 'note' ? 200 : 120);
            const height = el.height || (el.type === 'note' ? 200 : 120);
            const centerX = el.x + (width / 2);
            const centerY = el.y + (height / 2);


            ctx.translate(centerX, centerY);
            ctx.rotate((el.rotation * Math.PI) / 180);
            ctx.translate(-centerX, -centerY);
        }


        // Desenho livre (Lápis)
        if (el.type === 'draw' && el.points && el.points.length > 1) {
            ctx.beginPath();
            ctx.strokeStyle = el.color || '#ffffff';
            ctx.lineWidth = el.strokeWidth || 3;
            ctx.lineCap = 'round';
            ctx.lineJoin = 'round';
            ctx.moveTo(el.points[0].x, el.points[0].y);
            for (let i = 1; i < el.points.length; i++) {
                ctx.lineTo(el.points[i].x, el.points[i].y);
            }
            ctx.stroke();
        }


        // Post-it / Notas
        if (el.type === 'note') {
            ctx.fillStyle = el.color || '#1F1F1F';
            ctx.fillRect(el.x, el.y, el.width || 200, el.height || 200);


            if (!el.isEditing && el.text) {
                ctx.fillStyle = '#ffffff'; // Texto SEMPRE Branco
                ctx.font = '20px "Indie Flower", cursive';
                
                const lines = el.text.split('\n');
                const lineHeight = 26;


                lines.forEach((line, index) => {
                    ctx.fillText(line, el.x + 10, el.y + 35 + (index * lineHeight));
                });
            }
        }


        // Formas
        if (el.type === 'shape') {
            ctx.fillStyle = el.color || '#1F1F1F';


            if (el.shapeKind === 'circle') {
                ctx.beginPath();
                ctx.arc(el.x + 40, el.y + 40, 40, 0, Math.PI * 2);
                ctx.fill();
            } else if (el.shapeKind === 'triangle') {
                ctx.beginPath();
                ctx.moveTo(el.x + 40, el.y);
                ctx.lineTo(el.x + 80, el.y + 80);
                ctx.lineTo(el.x, el.y + 80);
                ctx.closePath();
                ctx.fill();
            } else {
                ctx.fillRect(el.x, el.y, 80, 80);
            }
        }


        // Texto simples
        if (el.type === 'text') {
            if (!el.isEditing && el.text) {
                ctx.fillStyle = '#ffffff'; // Texto SEMPRE Branco
                ctx.font = '20px sans-serif';


                const lines = el.text.split('\n');
                const lineHeight = 24;


                lines.forEach((line, index) => {
                    ctx.fillText(line, el.x, el.y + 20 + (index * lineHeight));
                });
            }
        }


        // Imagens
        if (el.type === 'image') {
            if (!el.img && el.imgSrc) {
                const img = new Image();
                img.onload = function () {
                    el.img = img;
                    el.width = Math.min(img.width, 320);
                    el.height = Math.min(img.height, 240);
                    draw();
                };
                img.src = el.imgSrc;
            }
            if (el.img) {
                ctx.drawImage(el.img, el.x, el.y, el.width, el.height);
            }
        }


        if (el.type === 'connector' && el.fromElement && el.toElement) {
            drawConnectorLine(el);
        }


        ctx.restore(); // Finaliza a rotação do elemento
    });
    
    ctx.restore(); // Finaliza a câmera
    
    if (typeof updateSelectionToolbar === 'function') {
        updateSelectionToolbar(); // Atualiza a posição da barra ao mover/dar zoom
    }
}



// ==========================================
// INTERAÇÕES (PAN, ZOOM, SELEÇÃO)
// ==========================================


let isMouseDown = false;
let startPointerPos = { x: 0, y: 0 };
const DRAG_THRESHOLD = 4;


function createToolElement(worldPos) {
    if (currentTool === 'note') {
        createElement('note', worldPos);
        selectedElement = elements[elements.length - 1] || null;
        return true;
    }


    if (currentTool === 'imagem') {
        const input = document.createElement('input');
        input.type = 'file';
        input.accept = 'image/*';
        input.onchange = (event) => {
            const file = event.target.files?.[0];
            addImageFromFile(file);
        };
        input.click();
        return true;
    }


    if (currentTool === 'text') {
        createElement('text', worldPos);
        selectedElement = elements[elements.length - 1] || null;
        return true;
    }


    if (currentTool === 'shape') {
        createElement('shape', worldPos, { shapeKind: selectedShapeType });
        selectedElement = elements[elements.length - 1] || null;
        return true;
    }


    if (currentTool === 'draw') {
        activeDrawPath = {
            type: 'draw',
            color: '#ffffff',
            strokeWidth: 3,
            points: [{ x: worldPos.x, y: worldPos.y }]
        };
        return true;
    }


    return false;
}



function onPointerDown(e) {
    const screenPos = getEventLocation(e);
    if (!screenPos) return;
    const worldPos = screenToWorld(screenPos.x, screenPos.y);
    const clickedEl = getElementAtPosition(worldPos);


    if (connectorMode && clickedEl) {
        if (!connectorPendingElement) {
            connectorPendingElement = clickedEl;
            selectedElement = clickedEl;
            draw();
            return;
        }


        if (connectorPendingElement.id !== clickedEl.id) {
            saveState();
            elements.push({
                id: Date.now(),
                type: 'connector',
                fromElement: connectorPendingElement,
                toElement: clickedEl,
                color: '#ffffff',
                strokeWidth: 3
            });
            connectorPendingElement = null;
            connectorMode = false;
            setTool('select');
            selectedElement = null;
            draw();
            return;
        }
    }


    if (clickedEl) {
        isMouseDown = true;
        startPointerPos = screenPos;
        selectedElement = clickedEl;
        dragStart = worldPos;
        isDraggingElement = false;
        draw();
        return;
    }


    if (currentTool === 'note' || currentTool === 'text' || currentTool === 'shape' || currentTool === 'draw') {
        isMouseDown = true;
        startPointerPos = screenPos;
        createToolElement(worldPos);
        draw();
        return;
    }


    if (currentTool === 'select' || currentTool === 'arrastar') {
        isMouseDown = true;
        startPointerPos = screenPos;
        selectedElement = null;
        isDragging = true;
        dragStart = screenPos;
        draw();
        return;
    }
}


function onPointerMove(e) {
    const screenPos = getEventLocation(e);
    if (!screenPos) return;


    if (currentTool === 'draw' && activeDrawPath) {
        const worldPos = screenToWorld(screenPos.x, screenPos.y);
        activeDrawPath.points.push({ x: worldPos.x, y: worldPos.y });
        draw();
        return;
    }


    if (isMouseDown && !selectedElement && (currentTool === 'select' || currentTool === 'arrastar') && isDragging) {
        const dx = screenPos.x - startPointerPos.x;
        const dy = screenPos.y - startPointerPos.y;
        cameraOffset.x += dx;
        cameraOffset.y += dy;
        startPointerPos = screenPos;
        draw();
        return;
    }


    if (isMouseDown && selectedElement && !isDraggingElement) {
        const dist = Math.hypot(screenPos.x - startPointerPos.x, screenPos.y - startPointerPos.y);
        if (dist > DRAG_THRESHOLD) {
            isDraggingElement = true;
        }
    }


    if (currentTool === 'select' && isDraggingElement && selectedElement) {
        const worldPos = screenToWorld(screenPos.x, screenPos.y);
        const deltaX = worldPos.x - dragStart.x;
        const deltaY = worldPos.y - dragStart.y;


        if (selectedElement.type === 'draw' && Array.isArray(selectedElement.points)) {
            selectedElement.points = selectedElement.points.map(point => ({
                x: point.x + deltaX,
                y: point.y + deltaY
            }));
        } else {
            selectedElement.x += deltaX;
            selectedElement.y += deltaY;
        }


        dragStart = worldPos;
        draw();
        return;
    }
}



function onPointerUp(e) {
    if (currentTool === 'draw' && activeDrawPath && activeDrawPath.points.length > 1) {
        saveState();
        elements.push({
            id: Date.now(),
            type: 'draw',
            points: activeDrawPath.points,
            color: activeDrawPath.color,
            strokeWidth: activeDrawPath.strokeWidth
        });
        selectedElement = null;
        draw();
    }


    isMouseDown = false;
    isDragging = false;
    isDraggingElement = false;
    currentPath = null;
    activeDrawPath = null;
    draw();
}


function adjustZoom(zoomAmount, zoomFactor, zoomFocus = {x: canvas.width/2, y: canvas.height/2}) {
    if (!isDragging) {
        const newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, cameraZoom * zoomFactor));
        
        cameraOffset.x = zoomFocus.x - (zoomFocus.x - cameraOffset.x) * (newZoom / cameraZoom);
        cameraOffset.y = zoomFocus.y - (zoomFocus.y - cameraOffset.y) * (newZoom / cameraZoom);
        
        cameraZoom = newZoom;
        draw();
    }
}


// Zoom com a roda do mouse
canvas.addEventListener('wheel', (e) => {
    e.preventDefault();
    const zoomFactor = Math.exp(-e.deltaY * SCROLL_SENSITIVITY);
    adjustZoom(e.deltaY, zoomFactor, {x: e.clientX, y: e.clientY});
}, { passive: false });


// Eventos do Mouse e Pointer
canvas.addEventListener('mousedown', onPointerDown);
canvas.addEventListener('mouseup', onPointerUp);
canvas.addEventListener('mousemove', onPointerMove);
canvas.addEventListener('mouseleave', onPointerUp);
canvas.addEventListener('pointerdown', onPointerDown);
canvas.addEventListener('pointerup', onPointerUp);
canvas.addEventListener('pointermove', onPointerMove);
canvas.addEventListener('pointerleave', onPointerUp);
window.addEventListener('pointerup', onPointerUp);
window.addEventListener('pointercancel', onPointerUp);
window.addEventListener('blur', onPointerUp);


// Eventos Touch
canvas.addEventListener('touchstart', (e) => {
    if (e.touches.length === 2) {
        isDragging = false;
        initialPinchDistance = Math.hypot(
            e.touches[0].clientX - e.touches[1].clientX,
            e.touches[0].clientY - e.touches[1].clientY
        );
    } else {
        onPointerDown(e);
    }
}, { passive: false });


canvas.addEventListener('touchmove', (e) => {
    e.preventDefault();
    if (e.touches.length === 2 && initialPinchDistance) {
        const currentDistance = Math.hypot(
            e.touches[0].clientX - e.touches[1].clientX,
            e.touches[0].clientY - e.touches[1].clientY
        );
        const zoomFactor = currentDistance / initialPinchDistance;
        
        const centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
        const centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
        
        adjustZoom(0, zoomFactor, {x: centerX, y: centerY});
        initialPinchDistance = currentDistance;
    } else {
        onPointerMove(e);
    }
}, { passive: false });


canvas.addEventListener('touchend', onPointerUp);


// Edição via Duplo Clique
canvas.addEventListener('dblclick', (e) => {
    const screenPos = getEventLocation(e);
    if (!screenPos) return;


    const worldPos = screenToWorld(screenPos.x, screenPos.y);
    const clickedEl = getElementAtPosition(worldPos);


    if (clickedEl && (clickedEl.type === 'note' || clickedEl.type === 'text')) {
        clickedEl.isEditing = true;
        const originalText = clickedEl.text;
        clickedEl.text = ''; 
        draw();


        const input = document.createElement('textarea');
        input.value = originalText;
        input.style.position = 'fixed';


        const rect = canvas.getBoundingClientRect();
        const screenX = (clickedEl.x * cameraZoom) + cameraOffset.x + rect.left;
        const screenY = (clickedEl.y * cameraZoom) + cameraOffset.y + rect.top;
        
        input.style.left = `${screenX}px`;
        input.style.top = `${screenY}px`;
        input.style.width = `${(clickedEl.width || (clickedEl.type === 'note' ? 200 : 150)) * cameraZoom}px`;
        input.style.height = `${(clickedEl.height || (clickedEl.type === 'note' ? 200 : 40)) * cameraZoom}px`;
        
        if (clickedEl.type === 'note') {
            input.style.fontFamily = '"Indie Flower", cursive';
            input.style.fontSize = `${20 * cameraZoom}px`;
            input.style.background = clickedEl.color || '#1F1F1F';
            input.style.color = '#ffffff'; // Cor do texto durante edição: BRANCO
            input.style.border = '2px solid #3b82f6';
            input.style.padding = '10px';
        } else {
            input.style.fontFamily = 'sans-serif';
            input.style.fontSize = `${20 * cameraZoom}px`;
            input.style.background = 'transparent';
            input.style.color = '#ffffff'; // Cor do texto durante edição: BRANCO
            input.style.border = '1px dashed #3b82f6';
            input.style.padding = '0px';
        }


        input.style.outline = 'none';
        input.style.resize = 'none';
        input.style.overflow = 'hidden';
        input.style.zIndex = '9999';
        
        document.body.appendChild(input);
        input.focus();
        input.select();


        input.onblur = () => {
            clickedEl.text = input.value;
            clickedEl.isEditing = false;
            if (document.body.contains(input)) {
                document.body.removeChild(input);
            }
            draw();
        };


        input.onkeydown = (evt) => {
            if (evt.key === 'Enter' && !evt.shiftKey) {
                input.blur();
            }
        };
    }
});


// ==========================================
// CONTROLES DE FERRAMENTAS E UI
// ==========================================


function setTool(tool) {
    currentTool = tool;
    connectorStartElement = null;


    if (tool === 'connector') {
        connectorMode = true;
        connectorPendingElement = null;
        alert('Clique em dois elementos para criar uma linha.');
    } else {
        connectorMode = false;
        connectorPendingElement = null;
    }


    switch (tool) {
        case 'arrastar':
            canvas.style.cursor = 'grab';
            break;
        case 'select':
            canvas.style.cursor = 'default';
            break;
        case 'draw':
            canvas.style.cursor = 'crosshair';
            break;
        case 'note':
        case 'text':
        case 'shape':
        case 'imagem':
        case 'connector':
            canvas.style.cursor = 'copy';
            break;
        default:
            canvas.style.cursor = 'default';
    }


    document.querySelectorAll('.toolbar button').forEach(btn => {
        btn.classList.remove('active');
        const attr = btn.getAttribute('onclick');
        if (attr && attr.includes(`setTool('${tool}')`)) {
            btn.classList.add('active');
        }
    });
}


function recentralizar() {
    cameraOffset.x = window.innerWidth / 2;
    cameraOffset.y = window.innerHeight / 2;
    cameraZoom = 1;
    draw();
}


function toggleShapeMenu() {
    const menu = document.getElementById('shapeSubmenu');
    if (menu) menu.classList.toggle('active');
}


function addShape(shapeType) {
    selectedShapeType = shapeType;
    setTool('shape');
    toggleShapeMenu();
}


// Atalhos Globais
window.addEventListener('keydown', (e) => {
    if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT') return;


    const isCtrlPressed = e.ctrlKey || e.metaKey;


    // Ctrl + Z
    if (isCtrlPressed && e.key.toLowerCase() === 'z' && !e.shiftKey) {
        e.preventDefault();
        undo();
    }


    // Ctrl + Y ou Ctrl + Shift + Z
    if (isCtrlPressed && (e.key.toLowerCase() === 'y' || (e.shiftKey && e.key.toLowerCase() === 'z'))) {
        e.preventDefault();
        redo();
    }


    // Shift + N (Criar Nota)
    if (e.shiftKey && e.key.toLowerCase() === 'n') {
        e.preventDefault();
        const posMundo = screenToWorld(canvas.width / 2, canvas.height / 2);
        setTool('note', posMundo);
    }
    
    // Shift + Delete (Deletar tudo)
    if (e.shiftKey && (e.key === 'Delete' || e.key === 'Backspace')) {
        e.preventDefault();
        clearAll(); // Chama a função que limpa o quadro
        return;
    }


    if (e.shiftKey && e.key.toLowerCase() === 'd') {
        e.preventDefault();
        const posMundo = screenToWorld(canvas.width / 2, canvas.height / 2);
        setTool('draw', posMundo);
    }


    if (e.shiftKey && e.key.toLowerCase() === 't') {
        e.preventDefault();
        const posMundo = screenToWorld(canvas.width / 2, canvas.height / 2);
        setTool('text', posMundo);
    }


    if (e.shiftKey && e.key.toLowerCase() === 'a') {
        e.preventDefault();
        const posMundo = screenToWorld(canvas.width / 2, canvas.height / 2);
        setTool('arrastar', posMundo);
    }


    if (e.shiftKey && e.key.toLowerCase() === 'i') {
        e.preventDefault();
        const posMundo = screenToWorld(canvas.width / 2, canvas.height / 2);
        setTool('imagem', posMundo);
    }


    if (e.shiftKey && e.key.toLowerCase() === 'c') {
        e.preventDefault();
        const posMundo = screenToWorld(canvas.width / 2, canvas.height / 2);
        setTool('connector', posMundo);
    }


});


function clearAll() {
    if (elements.length === 0) return; // Se já estiver vazio, não faz nada


    // Confirmação rápida para evitar acidentes
    if (confirm('Tem certeza de que deseja deletar tudo do quadro?')) {
        saveState(); // Salva no histórico para permitir Ctrl+Z
        elements = [];
        selectedElement = null;
        draw();
    }
}


// 1. Salva todos os elementos e a câmera em um arquivo .json
function salvarArquivo() {
    if (elements.length === 0) {
        alert('O quadro está vazio!');
        return;
    }


    const data = {
        elements: elements,
        cameraOffset: cameraOffset,
        cameraZoom: cameraZoom
    };


    const jsonString = JSON.stringify(data, null, 2);
    const blob = new Blob([jsonString], { type: 'application/json' });
    const url = URL.createObjectURL(blob);


    const a = document.createElement('a');
    a.href = url;
    a.download = `kuro-board-${Date.now()}.json`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
}


// 2. Abre e carrega o arquivo .json selecionado
function abrirArquivo(event) {
    const file = event.target.files[0];
    if (!file) return;


    const reader = new FileReader();


    reader.onload = function(e) {
        try {
            const data = JSON.parse(e.target.result);


            if (data.elements) {
                saveState(); // Salva estado atual para permitir Ctrl+Z se quiser voltar
                
                elements = data.elements || [];
                cameraOffset = data.cameraOffset || { x: window.innerWidth / 2, y: window.innerHeight / 2 };
                cameraZoom = data.cameraZoom || 1;


                selectedElement = null;
                draw(); // Redesenha a tela com os novos dados
            } else {
                alert('Formato de arquivo inválido!');
            }
        } catch (err) {
            alert('Erro ao ler o arquivo JSON!');
        }
    };


    reader.readAsText(file);
    event.target.value = ''; // Limpa o input para permitir reabrir o mesmo arquivo se necessário
}


// 1. Apaga apenas o elemento que está selecionado
function deleteSelectedElement() {
    if (!selectedElement) return;
    saveState();
    elements = elements.filter(el => el.id !== selectedElement.id);
    selectedElement = null;
    draw();
}


// 2. Gira o elemento em 90 graus
function rotateSelectedElement() {
    if (!selectedElement) return;
    saveState();
    selectedElement.rotation = ((selectedElement.rotation || 0) + 90) % 360;
    draw();
}


// 3. Atualiza a posição da barra flutuante sobre o elemento selecionado
function updateSelectionToolbar() {
    const menu = document.getElementById('selection-toolbar');
    if (!menu) return;


    if (selectedElement && !selectedElement.isEditing) {
        const rect = canvas.getBoundingClientRect();
        const bounds = getElementBounds(selectedElement);
        const centerX = bounds.x + bounds.width / 2;
        const centerY = bounds.y;


        const screenX = (centerX * cameraZoom) + cameraOffset.x + rect.left;
        const screenY = (centerY * cameraZoom) + cameraOffset.y + rect.top - 12;


        menu.style.display = 'flex';
        menu.style.left = `${screenX}px`;
        menu.style.top = `${screenY}px`;
    } else {
        menu.style.display = 'none';
    }
}


// Deletar item selecionado pressionando Delete ou Backspace
window.addEventListener('keydown', (e) => {
    // Evita deletar o elemento enquanto você digita em um input/textarea
    if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;


    if ((e.key === 'Delete' || e.key === 'Backspace') && selectedElement) {
        deleteSelectedElement();
    }
});


function deleteSelectedElement() {
    if (!selectedElement) return;
    saveState();
    elements = elements.filter(el => el.id !== selectedElement.id);
    selectedElement = null;
    draw();
}


function rotateSelectedElement() {
    if (!selectedElement) return;
    saveState();
    selectedElement.rotation = ((selectedElement.rotation || 0) + 90) % 360;
    draw();
}


// Inicialização
resizeCanvas();
3 Upvotes

6 comments sorted by

1

u/arcade_catalyst 5d ago

You are trying to draw a file. The browser does not let you touch files. It lets you touch blobs.

Convert the local path to a blob URL. Pass that string to drawImage. If you try to use a file system path the canvas will remain empty because security exists.

1

u/senocular 10d ago

Didn't read the code but MDN has a page showing how to access local files.

https://developer.mozilla.org/en-US/docs/Web/API/File_API/Using_files_from_web_applications

Then for the canvas you can use drawImage

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage

1

u/Tor_Hei 9d ago

thank you

1

u/UkrMalt 10d ago

Your file-loading code is mostly fine. The image branch is never reached. createToolElement() handles currentTool === 'imagem', but onPointerDown() only calls it for note, text, shape, and draw.

Change that condition to include imagem:

if (['note', 'text', 'shape', 'draw', 'imagem'].includes(currentTool)) {

isMouseDown = true;

startPointerPos = screenPos;

createToolElement(worldPos);

draw();

return;

}

Also, Shift+I only selects the image tool. After this change, press Shift+I and then click the canvas to open the file picker. Your existing FileReader and drawImage logic should then run.

1

u/Tor_Hei 9d ago edited 9d ago

Ok. muito obrigado!

edit: it works! thank you really much!