my-multi-notes / index.html
LJMolotov's picture
Add 3 files
a269c1b verified
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Excel Clone Avanzado</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.cell.selected {
box-shadow: inset 0 0 0 2px #3b82f6;
}
.cell.editing {
background-color: #1e293b;
box-shadow: inset 0 0 0 2px #10b981;
}
.column-resize-handle {
position: absolute;
right: -2px;
top: 0;
width: 4px;
height: 100%;
background-color: transparent;
cursor: col-resize;
z-index: 10;
}
.row-resize-handle {
position: absolute;
bottom: -2px;
left: 0;
width: 100%;
height: 4px;
background-color: transparent;
cursor: row-resize;
z-index: 10;
}
.scroll-shadow-right {
box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, 0.4);
}
.scroll-shadow-bottom {
box-shadow: inset 0 -10px 8px -8px rgba(0, 0, 0, 0.4);
}
.tab-active {
border-bottom: 2px solid #3b82f6;
color: #3b82f6;
}
.format-button.active {
background-color: #374151;
}
.color-preview {
width: 16px;
height: 16px;
display: inline-block;
border: 1px solid #6b7280;
vertical-align: middle;
margin-left: 4px;
}
</style>
</head>
<body class="bg-gray-800 text-gray-100 h-screen flex flex-col overflow-hidden">
<!-- Toolbar Superior -->
<div class="bg-gray-900 p-2 flex items-center border-b border-gray-700">
<!-- Archivo -->
<div class="relative group">
<button id="file-menu" class="px-3 py-1 rounded hover:bg-gray-700 text-blue-400 font-medium">
<i class="fas fa-file mr-1"></i> Archivo
</button>
<div class="absolute hidden group-hover:block bg-gray-800 border border-gray-700 rounded shadow-lg z-20 w-48">
<button id="new-file" class="block w-full text-left px-4 py-2 hover:bg-gray-700 text-green-400">
<i class="fas fa-file-circle-plus mr-2"></i> Nuevo
</button>
<button id="open-file" class="block w-full text-left px-4 py-2 hover:bg-gray-700 text-yellow-400">
<i class="fas fa-folder-open mr-2"></i> Abrir
</button>
<button id="save-file" class="block w-full text-left px-4 py-2 hover:bg-gray-700 text-purple-400">
<i class="fas fa-save mr-2"></i> Guardar
</button>
<div class="border-t border-gray-700"></div>
<button id="export-pdf" class="block w-full text-left px-4 py-2 hover:bg-gray-700 text-red-400">
<i class="fas fa-file-pdf mr-2"></i> Exportar PDF
</button>
</div>
</div>
<!-- Edición -->
<div class="relative group ml-2">
<button class="px-3 py-1 rounded hover:bg-gray-700 text-green-400 font-medium">
<i class="fas fa-edit mr-1"></i> Edición
</button>
<div class="absolute hidden group-hover:block bg-gray-800 border border-gray-700 rounded shadow-lg z-20 w-48">
<button id="undo" class="block w-full text-left px-4 py-2 hover:bg-gray-700 text-blue-400">
<i class="fas fa-undo mr-2"></i> Deshacer
</button>
<button id="redo" class="block w-full text-left px-4 py-2 hover:bg-gray-700 text-purple-400">
<i class="fas fa-redo mr-2"></i> Rehacer
</button>
<div class="border-t border-gray-700"></div>
<button id="cut" class="block w-full text-left px-4 py-2 hover:bg-gray-700 text-yellow-400">
<i class="fas fa-cut mr-2"></i> Cortar
</button>
<button id="copy" class="block w-full text-left px-4 py-2 hover:bg-gray-700 text-pink-400">
<i class="fas fa-copy mr-2"></i> Copiar
</button>
<button id="paste" class="block w-full text-left px-4 py-2 hover:bg-gray-700 text-indigo-400">
<i class="fas fa-paste mr-2"></i> Pegar
</button>
<div class="border-t border-gray-700"></div>
<button id="clear-contents" class="block w-full text-left px-4 py-2 hover:bg-gray-700 text-red-400">
<i class="fas fa-eraser mr-2"></i> Limpiar contenido
</button>
<button id="clear-formats" class="block w-full text-left px-4 py-2 hover:bg-gray-700 text-orange-400">
<i class="fas fa-remove-format mr-2"></i> Limpiar formato
</button>
</div>
</div>
<!-- Formato -->
<div class="flex items-center space-x-2 ml-4">
<select id="font-family" class="bg-gray-800 border border-gray-700 rounded px-2 py-1 text-sm text-white hover:bg-gray-700">
<option>Calibri</option>
<option>Arial</option>
<option>Times New Roman</option>
<option>Courier New</option>
<option>Verdana</option>
</select>
<select id="font-size" class="bg-gray-800 border border-gray-700 rounded px-2 py-1 text-sm text-white hover:bg-gray-700">
<option>8</option>
<option>10</option>
<option>11</option>
<option selected>12</option>
<option>14</option>
<option>16</option>
<option>18</option>
<option>20</option>
<option>24</option>
</select>
<button id="bold" class="p-2 rounded hover:bg-gray-700 text-pink-400 format-button">
<i class="fas fa-bold"></i>
</button>
<button id="italic" class="p-2 rounded hover:bg-gray-700 text-red-400 format-button">
<i class="fas fa-italic"></i>
</button>
<button id="underline" class="p-2 rounded hover:bg-gray-700 text-orange-400 format-button">
<i class="fas fa-underline"></i>
</button>
<div class="border-l border-gray-700 h-6 mx-1"></div>
<button id="align-left" class="p-2 rounded hover:bg-gray-700 text-cyan-400 format-button">
<i class="fas fa-align-left"></i>
</button>
<button id="align-center" class="p-2 rounded hover:bg-gray-700 text-emerald-400 format-button">
<i class="fas fa-align-center"></i>
</button>
<button id="align-right" class="p-2 rounded hover:bg-gray-700 text-blue-400 format-button">
<i class="fas fa-align-right"></i>
</button>
<div class="border-l border-gray-700 h-6 mx-1"></div>
<div class="relative">
<button id="text-color" class="p-2 rounded hover:bg-gray-700 text-yellow-400 flex items-center">
<i class="fas fa-font"></i>
<span id="text-color-preview" class="color-preview"></span>
</button>
</div>
<div class="relative">
<button id="fill-color" class="p-2 rounded hover:bg-gray-700 text-green-400 flex items-center">
<i class="fas fa-fill-drip"></i>
<span id="fill-color-preview" class="color-preview"></span>
</button>
</div>
<button id="border-style" class="p-2 rounded hover:bg-gray-700 text-purple-400">
<i class="fas fa-border-all"></i>
</button>
<button id="remove-colors" class="p-2 rounded hover:bg-gray-700 text-red-400">
<i class="fas fa-palette"></i> <i class="fas fa-slash"></i>
</button>
</div>
<!-- Fórmulas y Datos -->
<div class="ml-auto flex items-center space-x-2">
<button id="insert-function" class="px-3 py-1 rounded hover:bg-gray-700 text-indigo-400">
<i class="fas fa-sigma mr-1"></i> Fx
</button>
<button id="sort-asc" class="p-2 rounded hover:bg-gray-700 text-blue-400">
<i class="fas fa-sort-amount-up"></i>
</button>
<button id="sort-desc" class="p-2 rounded hover:bg-gray-700 text-red-400">
<i class="fas fa-sort-amount-down"></i>
</button>
<button id="filter" class="p-2 rounded hover:bg-gray-700 text-rose-400">
<i class="fas fa-filter"></i>
</button>
<button id="chart" class="p-2 rounded hover:bg-gray-700 text-green-400">
<i class="fas fa-chart-line"></i>
</button>
</div>
</div>
<!-- Pestañas de Hojas -->
<div class="bg-gray-900 p-1 flex items-center border-b border-gray-700">
<button id="add-sheet" class="px-2 py-1 rounded hover:bg-gray-700 text-green-400">
<i class="fas fa-plus"></i>
</button>
<div class="flex ml-2 overflow-x-auto">
<button class="px-3 py-1 rounded-l hover:bg-gray-700 text-sm font-medium tab-active">
Hoja1
</button>
<button class="px-3 py-1 hover:bg-gray-700 text-sm font-medium text-gray-400">
Hoja2
</button>
<button class="px-3 py-1 rounded-r hover:bg-gray-700 text-sm font-medium text-gray-400">
Hoja3
</button>
</div>
</div>
<!-- Formula Bar -->
<div class="bg-gray-900 p-1 flex items-center border-b border-gray-700">
<div id="cell-reference" class="w-16 text-center text-sm font-mono text-blue-400">A1</div>
<div class="flex-1 flex">
<input type="text" id="formula-input" class="bg-gray-800 border border-gray-700 rounded-l px-2 py-1 text-sm w-full font-mono focus:outline-none focus:ring-1 focus:ring-blue-500" placeholder="Introduce fórmula...">
<button id="apply-formula" class="bg-blue-600 hover:bg-blue-700 px-3 rounded-r">
<i class="fas fa-check"></i>
</button>
</div>
<div class="ml-2 text-xs text-gray-400">
<span id="formula-help">F2 para editar celda, Enter para confirmar</span>
</div>
</div>
<!-- Main Grid -->
<div class="flex-1 flex overflow-hidden">
<!-- Row Numbers -->
<div class="bg-gray-900 border-r border-gray-700 overflow-hidden" id="row-numbers">
<div class="w-10 h-8 flex items-center justify-center text-xs text-gray-400 border-b border-gray-700"></div>
<!-- Rows will be added dynamically -->
</div>
<!-- Column Headers + Grid -->
<div class="flex-1 flex flex-col overflow-hidden">
<!-- Column Headers -->
<div class="bg-gray-900 border-b border-gray-700 flex" id="column-headers">
<!-- Columns will be added dynamically -->
</div>
<!-- Grid Container -->
<div class="flex-1 overflow-auto relative scroll-shadow-right scroll-shadow-bottom" id="grid-container">
<div id="grid" class="absolute"></div>
</div>
</div>
</div>
<!-- Status Bar -->
<div class="bg-gray-900 p-1 flex items-center border-t border-gray-700 text-xs">
<div class="flex space-x-4">
<span id="status" class="text-green-400">Listo</span>
<span id="num-lock" class="text-yellow-400 hidden">NUM</span>
<span id="caps-lock" class="text-blue-400 hidden">MAYÚS</span>
<span id="selection-info" class="text-purple-400">1 celda seleccionada</span>
</div>
<div class="ml-auto flex space-x-2">
<span class="text-pink-400">Zoom: 100%</span>
<button id="zoom-out" class="text-pink-400 hover:bg-gray-700 px-1 rounded">-</button>
<button id="zoom-in" class="text-pink-400 hover:bg-gray-700 px-1 rounded">+</button>
<button id="zoom-reset" class="text-pink-400 hover:bg-gray-700 px-1 rounded">100%</button>
</div>
</div>
<!-- Color Picker (hidden by default) -->
<div id="color-picker" class="hidden absolute bg-gray-800 border border-gray-700 rounded shadow-lg p-2 z-50">
<div class="grid grid-cols-8 gap-1">
<div class="w-4 h-4 bg-red-500 hover:border hover:border-white cursor-pointer" data-color="#ef4444"></div>
<div class="w-4 h-4 bg-orange-500 hover:border hover:border-white cursor-pointer" data-color="#f97316"></div>
<div class="w-4 h-4 bg-yellow-500 hover:border hover:border-white cursor-pointer" data-color="#eab308"></div>
<div class="w-4 h-4 bg-green-500 hover:border hover:border-white cursor-pointer" data-color="#22c55e"></div>
<div class="w-4 h-4 bg-emerald-500 hover:border hover:border-white cursor-pointer" data-color="#10b981"></div>
<div class="w-4 h-4 bg-cyan-500 hover:border hover:border-white cursor-pointer" data-color="#06b6d4"></div>
<div class="w-4 h-4 bg-blue-500 hover:border hover:border-white cursor-pointer" data-color="#3b82f6"></div>
<div class="w-4 h-4 bg-indigo-500 hover:border hover:border-white cursor-pointer" data-color="#6366f1"></div>
<div class="w-4 h-4 bg-purple-500 hover:border hover:border-white cursor-pointer" data-color="#8b5cf6"></div>
<div class="w-4 h-4 bg-pink-500 hover:border hover:border-white cursor-pointer" data-color="#ec4899"></div>
<div class="w-4 h-4 bg-rose-500 hover:border hover:border-white cursor-pointer" data-color="#f43f5e"></div>
<div class="w-4 h-4 bg-amber-500 hover:border hover:border-white cursor-pointer" data-color="#f59e0b"></div>
<div class="w-4 h-4 bg-lime-500 hover:border hover:border-white cursor-pointer" data-color="#84cc16"></div>
<div class="w-4 h-4 bg-teal-500 hover:border hover:border-white cursor-pointer" data-color="#14b8a6"></div>
<div class="w-4 h-4 bg-sky-500 hover:border hover:border-white cursor-pointer" data-color="#0ea5e9"></div>
<div class="w-4 h-4 bg-violet-500 hover:border hover:border-white cursor-pointer" data-color="#8b5cf6"></div>
</div>
<div class="mt-2 flex justify-between">
<button id="color-picker-cancel" class="px-2 py-1 text-xs bg-gray-700 rounded hover:bg-gray-600">Cancelar</button>
<button id="color-picker-apply" class="px-2 py-1 text-xs bg-blue-600 rounded hover:bg-blue-700">Aplicar</button>
<button id="color-picker-remove" class="px-2 py-1 text-xs bg-red-600 rounded hover:bg-red-700">Quitar</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Configuration
const ROWS = 50;
const COLS = 26;
const CELL_WIDTH = 100;
const CELL_HEIGHT = 25;
const HEADER_HEIGHT = 25;
// State
let selectedCell = null;
let editingCell = null;
let data = {};
let formulas = {};
let cellWidths = {};
let cellHeights = {};
let cellStyles = {};
let activeSheet = 'Hoja1';
let sheets = {
'Hoja1': { data: {}, formulas: {}, cellStyles: {} },
'Hoja2': { data: {}, formulas: {}, cellStyles: {} },
'Hoja3': { data: {}, formulas: {}, cellStyles: {} }
};
let history = [];
let historyIndex = -1;
let isBold = false;
let isItalic = false;
let isUnderline = false;
let textAlign = 'left';
let currentColorPickerType = null;
let defaultTextColor = '#ffffff'; // Blanco
let defaultFillColor = 'transparent'; // Transparente
// DOM Elements
const grid = document.getElementById('grid');
const gridContainer = document.getElementById('grid-container');
const columnHeaders = document.getElementById('column-headers');
const rowNumbers = document.getElementById('row-numbers');
const formulaInput = document.getElementById('formula-input');
const cellReference = document.getElementById('cell-reference');
const statusElement = document.getElementById('status');
const selectionInfo = document.getElementById('selection-info');
const colorPicker = document.getElementById('color-picker');
const numLockElement = document.getElementById('num-lock');
const capsLockElement = document.getElementById('caps-lock');
const textColorPreview = document.getElementById('text-color-preview');
const fillColorPreview = document.getElementById('fill-color-preview');
// Initialize grid
function initGrid() {
// Set grid dimensions
grid.style.width = `${COLS * CELL_WIDTH}px`;
grid.style.height = `${ROWS * CELL_HEIGHT}px`;
// Create column headers (A, B, C...)
for (let col = 0; col < COLS; col++) {
const colHeader = document.createElement('div');
const colLetter = String.fromCharCode(65 + col);
colHeader.className = 'h-8 flex items-center justify-center text-xs text-gray-400 border-b border-gray-700 relative';
colHeader.style.width = `${CELL_WIDTH}px`;
colHeader.textContent = colLetter;
// Add resize handle
const resizeHandle = document.createElement('div');
resizeHandle.className = 'column-resize-handle';
resizeHandle.dataset.col = col;
resizeHandle.addEventListener('mousedown', startColumnResize);
colHeader.appendChild(resizeHandle);
columnHeaders.appendChild(colHeader);
}
// Create row numbers (1, 2, 3...)
for (let row = 1; row <= ROWS; row++) {
const rowNumber = document.createElement('div');
rowNumber.className = 'w-10 h-8 flex items-center justify-center text-xs text-gray-400 border-b border-gray-700 relative';
rowNumber.textContent = row;
// Add resize handle
const resizeHandle = document.createElement('div');
resizeHandle.className = 'row-resize-handle';
resizeHandle.dataset.row = row - 1;
resizeHandle.addEventListener('mousedown', startRowResize);
rowNumber.appendChild(resizeHandle);
rowNumbers.appendChild(rowNumber);
}
// Create cells
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLS; col++) {
const cell = document.createElement('div');
const cellId = `${String.fromCharCode(65 + col)}${row + 1}`;
cell.className = 'cell absolute bg-gray-800 border border-gray-700 p-1 overflow-hidden text-sm';
cell.style.width = `${CELL_WIDTH}px`;
cell.style.height = `${CELL_HEIGHT}px`;
cell.style.left = `${col * CELL_WIDTH}px`;
cell.style.top = `${row * CELL_HEIGHT}px`;
cell.dataset.row = row;
cell.dataset.col = col;
cell.dataset.id = cellId;
cell.addEventListener('click', handleCellClick);
cell.addEventListener('dblclick', handleCellDoubleClick);
grid.appendChild(cell);
}
}
// Set default color previews
textColorPreview.style.backgroundColor = defaultTextColor;
fillColorPreview.style.backgroundColor = defaultFillColor;
// Add initial state to history
saveState();
}
// Handle cell click
function handleCellClick(e) {
const cell = e.currentTarget;
const cellId = cell.dataset.id;
// Remove previous selection
if (selectedCell) {
selectedCell.classList.remove('selected');
}
// Set new selection
selectedCell = cell;
cell.classList.add('selected');
// Update formula bar
formulaInput.value = formulas[cellId] || data[cellId] || '';
formulaInput.focus();
// Update cell reference
cellReference.textContent = cellId;
// Update selection info
selectionInfo.textContent = "1 celda seleccionada";
// Update status
statusElement.textContent = "Listo";
// Update format buttons based on cell style
updateFormatButtons(cellId);
}
// Update format buttons based on cell style
function updateFormatButtons(cellId) {
// Reset all format buttons
document.querySelectorAll('.format-button').forEach(btn => btn.classList.remove('active'));
if (cellStyles[cellId]) {
const style = cellStyles[cellId];
// Bold
if (style.bold) {
document.getElementById('bold').classList.add('active');
}
// Italic
if (style.italic) {
document.getElementById('italic').classList.add('active');
}
// Underline
if (style.underline) {
document.getElementById('underline').classList.add('active');
}
// Text align
if (style.textAlign) {
document.getElementById(`align-${style.textAlign}`).classList.add('active');
}
// Update color previews
if (style.color) {
textColorPreview.style.backgroundColor = style.color;
} else {
textColorPreview.style.backgroundColor = defaultTextColor;
}
if (style.backgroundColor) {
fillColorPreview.style.backgroundColor = style.backgroundColor;
} else {
fillColorPreview.style.backgroundColor = defaultFillColor;
}
}
}
// Handle cell double click (edit mode)
function handleCellDoubleClick(e) {
const cell = e.currentTarget;
const cellId = cell.dataset.id;
// Exit previous edit mode
if (editingCell) {
exitEditMode();
}
// Enter edit mode
editingCell = cell;
cell.classList.add('editing');
// Create input element
const input = document.createElement('input');
input.type = 'text';
input.className = 'w-full h-full bg-gray-800 text-white px-1 outline-none';
input.value = data[cellId] || '';
input.dataset.cellId = cellId;
// Apply cell styles to input
if (cellStyles[cellId]) {
if (cellStyles[cellId].bold) input.style.fontWeight = 'bold';
if (cellStyles[cellId].italic) input.style.fontStyle = 'italic';
if (cellStyles[cellId].underline) input.style.textDecoration = 'underline';
if (cellStyles[cellId].textAlign) input.style.textAlign = cellStyles[cellId].textAlign;
if (cellStyles[cellId].color) input.style.color = cellStyles[cellId].color;
if (cellStyles[cellId].backgroundColor) input.style.backgroundColor = cellStyles[cellId].backgroundColor;
if (cellStyles[cellId].fontSize) input.style.fontSize = cellStyles[cellId].fontSize + 'px';
if (cellStyles[cellId].fontFamily) input.style.fontFamily = cellStyles[cellId].fontFamily;
}
// Clear cell content and add input
cell.innerHTML = '';
cell.appendChild(input);
input.focus();
// Handle input events
input.addEventListener('blur', handleInputBlur);
input.addEventListener('keydown', handleInputKeyDown);
// Update status
statusElement.textContent = "Editando";
}
// Exit edit mode
function exitEditMode() {
if (!editingCell) return;
const cell = editingCell;
const cellId = cell.dataset.id;
const input = cell.querySelector('input');
if (input) {
// Save data
const value = input.value.trim();
if (value.startsWith('=')) {
formulas[cellId] = value;
try {
data[cellId] = evaluateFormula(value.substring(1));
} catch (e) {
data[cellId] = '#ERROR!';
}
} else {
data[cellId] = value;
delete formulas[cellId];
}
// Update cell display
cell.innerHTML = '';
cell.textContent = data[cellId] || '';
// Apply styles
applyCellStyles(cell, cellId);
}
cell.classList.remove('editing');
editingCell = null;
// Save state to history
saveState();
// Update status
statusElement.textContent = "Listo";
}
// Apply cell styles
function applyCellStyles(cell, cellId) {
// Reset to defaults first
cell.style = '';
cell.className = 'cell absolute bg-gray-800 border border-gray-700 p-1 overflow-hidden text-sm';
if (cellStyles[cellId]) {
const style = cellStyles[cellId];
if (style.bold) cell.style.fontWeight = 'bold';
if (style.italic) cell.style.fontStyle = 'italic';
if (style.underline) cell.style.textDecoration = 'underline';
if (style.textAlign) cell.style.textAlign = style.textAlign;
if (style.color) cell.style.color = style.color;
if (style.backgroundColor) cell.style.backgroundColor = style.backgroundColor;
if (style.fontSize) cell.style.fontSize = style.fontSize + 'px';
if (style.fontFamily) cell.style.fontFamily = style.fontFamily;
}
// Update color previews
updateColorPreviews(cellId);
}
// Update color previews
function updateColorPreviews(cellId) {
if (cellStyles[cellId]) {
if (cellStyles[cellId].color) {
textColorPreview.style.backgroundColor = cellStyles[cellId].color;
} else {
textColorPreview.style.backgroundColor = defaultTextColor;
}
if (cellStyles[cellId].backgroundColor) {
fillColorPreview.style.backgroundColor = cellStyles[cellId].backgroundColor;
} else {
fillColorPreview.style.backgroundColor = defaultFillColor;
}
} else {
textColorPreview.style.backgroundColor = defaultTextColor;
fillColorPreview.style.backgroundColor = defaultFillColor;
}
}
// Handle input blur (exit edit mode)
function handleInputBlur(e) {
exitEditMode();
}
// Handle input key events
function handleInputKeyDown(e) {
if (e.key === 'Enter') {
exitEditMode();
} else if (e.key === 'Escape') {
const input = e.currentTarget;
const cell = input.parentElement;
// Restore original content
cell.innerHTML = '';
cell.textContent = data[cell.dataset.id] || '';
applyCellStyles(cell, cell.dataset.id);
cell.classList.remove('editing');
editingCell = null;
// Update status
statusElement.textContent = "Listo";
}
}
// Simple formula evaluation (very basic implementation)
function evaluateFormula(formula) {
// Basic SUM implementation
if (formula.toUpperCase().startsWith('SUM(')) {
const range = formula.match(/\((.*?)\)/)[1];
const [start, end] = range.split(':');
let sum = 0;
const startCol = start.charCodeAt(0) - 65;
const startRow = parseInt(start.substring(1)) - 1;
const endCol = end.charCodeAt(0) - 65;
const endRow = parseInt(end.substring(1)) - 1;
for (let row = startRow; row <= endRow; row++) {
for (let col = startCol; col <= endCol; col++) {
const cellId = `${String.fromCharCode(65 + col)}${row + 1}`;
const value = parseFloat(data[cellId]) || 0;
sum += value;
}
}
return sum;
}
// Basic arithmetic
if (formula.includes('+')) {
const parts = formula.split('+');
return (parseFloat(parts[0]) || 0) + (parseFloat(parts[1]) || 0);
}
if (formula.includes('-')) {
const parts = formula.split('-');
return (parseFloat(parts[0]) || 0) - (parseFloat(parts[1]) || 0);
}
if (formula.includes('*')) {
const parts = formula.split('*');
return (parseFloat(parts[0]) || 0) * (parseFloat(parts[1]) || 0);
}
if (formula.includes('/')) {
const parts = formula.split('/');
return (parseFloat(parts[0]) || 0) / (parseFloat(parts[1]) || 1);
}
// Cell reference
if (/^[A-Z]+\d+$/.test(formula)) {
return data[formula] || 0;
}
return formula; // Return as is if not a recognized formula
}
// Handle column resize
let isResizingColumn = false;
let resizingCol = null;
let startX = 0;
let startWidth = 0;
function startColumnResize(e) {
e.preventDefault();
e.stopPropagation();
isResizingColumn = true;
resizingCol = parseInt(e.currentTarget.dataset.col);
startX = e.clientX;
startWidth = CELL_WIDTH;
document.addEventListener('mousemove', handleColumnResize);
document.addEventListener('mouseup', stopColumnResize);
// Update status
statusElement.textContent = "Redimensionando columna";
}
function handleColumnResize(e) {
if (!isResizingColumn) return;
const dx = e.clientX - startX;
const newWidth = Math.max(30, startWidth + dx);
// Update column width
const colLetter = String.fromCharCode(65 + resizingCol);
cellWidths[colLetter] = newWidth;
// Update header
columnHeaders.children[resizingCol].style.width = `${newWidth}px`;
// Update all cells in this column
const cells = document.querySelectorAll(`.cell[data-col="${resizingCol}"]`);
cells.forEach(cell => {
cell.style.width = `${newWidth}px`;
});
// Update grid width
updateGridDimensions();
}
function stopColumnResize() {
isResizingColumn = false;
document.removeEventListener('mousemove', handleColumnResize);
document.removeEventListener('mouseup', stopColumnResize);
// Save state to history
saveState();
// Update status
statusElement.textContent = "Listo";
}
// Handle row resize
let isResizingRow = false;
let resizingRow = null;
let startY = 0;
let startHeight = 0;
function startRowResize(e) {
e.preventDefault();
e.stopPropagation();
isResizingRow = true;
resizingRow = parseInt(e.currentTarget.dataset.row);
startY = e.clientY;
startHeight = CELL_HEIGHT;
document.addEventListener('mousemove', handleRowResize);
document.addEventListener('mouseup', stopRowResize);
// Update status
statusElement.textContent = "Redimensionando fila";
}
function handleRowResize(e) {
if (!isResizingRow) return;
const dy = e.clientY - startY;
const newHeight = Math.max(20, startHeight + dy);
// Update row height
cellHeights[resizingRow + 1] = newHeight;
// Update row number display
rowNumbers.children[resizingRow + 1].style.height = `${newHeight}px`;
// Update all cells in this row
const cells = document.querySelectorAll(`.cell[data-row="${resizingRow}"]`);
cells.forEach(cell => {
cell.style.height = `${newHeight}px`;
});
// Update grid height
updateGridDimensions();
}
function stopRowResize() {
isResizingRow = false;
document.removeEventListener('mousemove', handleRowResize);
document.removeEventListener('mouseup', stopRowResize);
// Save state to history
saveState();
// Update status
statusElement.textContent = "Listo";
}
// Update grid dimensions after resize
function updateGridDimensions() {
let totalWidth = 0;
for (let col = 0; col < COLS; col++) {
const colLetter = String.fromCharCode(65 + col);
totalWidth += cellWidths[colLetter] || CELL_WIDTH;
}
let totalHeight = 0;
for (let row = 0; row < ROWS; row++) {
totalHeight += cellHeights[row + 1] || CELL_HEIGHT;
}
grid.style.width = `${totalWidth}px`;
grid.style.height = `${totalHeight}px`;
}
// Handle formula input
formulaInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && selectedCell) {
const value = formulaInput.value.trim();
const cellId = selectedCell.dataset.id;
if (value.startsWith('=')) {
formulas[cellId] = value;
try {
data[cellId] = evaluateFormula(value.substring(1));
} catch (e) {
data[cellId] = '#ERROR!';
}
} else {
data[cellId] = value;
delete formulas[cellId];
}
selectedCell.textContent = data[cellId] || '';
formulaInput.blur();
// Save state to history
saveState();
}
});
// Apply formula button
document.getElementById('apply-formula').addEventListener('click', function() {
if (selectedCell) {
const value = formulaInput.value.trim();
const cellId = selectedCell.dataset.id;
if (value.startsWith('=')) {
formulas[cellId] = value;
try {
data[cellId] = evaluateFormula(value.substring(1));
} catch (e) {
data[cellId] = '#ERROR!';
}
} else {
data[cellId] = value;
delete formulas[cellId];
}
selectedCell.textContent = data[cellId] || '';
// Save state to history
saveState();
}
});
// Format buttons
document.getElementById('bold').addEventListener('click', function() {
if (selectedCell) {
const cellId = selectedCell.dataset.id;
// Initialize cell style if not exists
if (!cellStyles[cellId]) {
cellStyles[cellId] = {};
}
// Toggle bold
cellStyles[cellId].bold = !cellStyles[cellId].bold;
selectedCell.style.fontWeight = cellStyles[cellId].bold ? 'bold' : 'normal';
// Toggle active class
this.classList.toggle('active');
// Save state to history
saveState();
}
});
document.getElementById('italic').addEventListener('click', function() {
if (selectedCell) {
const cellId = selectedCell.dataset.id;
if (!cellStyles[cellId]) {
cellStyles[cellId] = {};
}
cellStyles[cellId].italic = !cellStyles[cellId].italic;
selectedCell.style.fontStyle = cellStyles[cellId].italic ? 'italic' : 'normal';
this.classList.toggle('active');
saveState();
}
});
document.getElementById('underline').addEventListener('click', function() {
if (selectedCell) {
const cellId = selectedCell.dataset.id;
if (!cellStyles[cellId]) {
cellStyles[cellId] = {};
}
cellStyles[cellId].underline = !cellStyles[cellId].underline;
selectedCell.style.textDecoration = cellStyles[cellId].underline ? 'underline' : 'none';
this.classList.toggle('active');
saveState();
}
});
// Alignment buttons
document.getElementById('align-left').addEventListener('click', function() {
if (selectedCell) {
const cellId = selectedCell.dataset.id;
if (!cellStyles[cellId]) {
cellStyles[cellId] = {};
}
cellStyles[cellId].textAlign = 'left';
selectedCell.style.textAlign = 'left';
// Update active state
document.querySelectorAll('.format-button').forEach(btn => btn.classList.remove('active'));
this.classList.add('active');
saveState();
}
});
document.getElementById('align-center').addEventListener('click', function() {
if (selectedCell) {
const cellId = selectedCell.dataset.id;
if (!cellStyles[cellId]) {
cellStyles[cellId] = {};
}
cellStyles[cellId].textAlign = 'center';
selectedCell.style.textAlign = 'center';
document.querySelectorAll('.format-button').forEach(btn => btn.classList.remove('active'));
this.classList.add('active');
saveState();
}
});
document.getElementById('align-right').addEventListener('click', function() {
if (selectedCell) {
const cellId = selectedCell.dataset.id;
if (!cellStyles[cellId]) {
cellStyles[cellId] = {};
}
cellStyles[cellId].textAlign = 'right';
selectedCell.style.textAlign = 'right';
document.querySelectorAll('.format-button').forEach(btn => btn.classList.remove('active'));
this.classList.add('active');
saveState();
}
});
// Font family change
document.getElementById('font-family').addEventListener('change', function() {
if (selectedCell) {
const cellId = selectedCell.dataset.id;
if (!cellStyles[cellId]) {
cellStyles[cellId] = {};
}
cellStyles[cellId].fontFamily = this.value;
selectedCell.style.fontFamily = this.value;
saveState();
}
});
// Font size change
document.getElementById('font-size').addEventListener('change', function() {
if (selectedCell) {
const cellId = selectedCell.dataset.id;
if (!cellStyles[cellId]) {
cellStyles[cellId] = {};
}
cellStyles[cellId].fontSize = parseInt(this.value);
selectedCell.style.fontSize = this.value + 'px';
saveState();
}
});
// Text color button
document.getElementById('text-color').addEventListener('click', function(e) {
currentColorPickerType = 'text';
showColorPicker(e.currentTarget);
});
// Fill color button
document.getElementById('fill-color').addEventListener('click', function(e) {
currentColorPickerType = 'background';
showColorPicker(e.currentTarget);
});
// Remove colors button
document.getElementById('remove-colors').addEventListener('click', function() {
if (selectedCell) {
const cellId = selectedCell.dataset.id;
if (cellStyles[cellId]) {
// Remove color properties
delete cellStyles[cellId].color;
delete cellStyles[cellId].backgroundColor;
// Apply changes
applyCellStyles(selectedCell, cellId);
// Save state
saveState();
// Update status
statusElement.textContent = "Colores eliminados";
setTimeout(() => statusElement.textContent = "Listo", 2000);
}
}
});
// Show color picker
function showColorPicker(target) {
const rect = target.getBoundingClientRect();
colorPicker.style.top = `${rect.bottom + 5}px`;
colorPicker.style.left = `${rect.left}px`;
colorPicker.classList.remove('hidden');
}
// Color picker selection
colorPicker.querySelectorAll('[data-color]').forEach(color => {
color.addEventListener('click', function() {
const selectedColor = this.dataset.color;
if (selectedCell && currentColorPickerType) {
const cellId = selectedCell.dataset.id;
if (!cellStyles[cellId]) {
cellStyles[cellId] = {};
}
if (currentColorPickerType === 'text') {
cellStyles[cellId].color = selectedColor;
selectedCell.style.color = selectedColor;
textColorPreview.style.backgroundColor = selectedColor;
} else {
cellStyles[cellId].backgroundColor = selectedColor;
selectedCell.style.backgroundColor = selectedColor;
fillColorPreview.style.backgroundColor = selectedColor;
}
saveState();
}
colorPicker.classList.add('hidden');
});
});
// Color picker remove
document.getElementById('color-picker-remove').addEventListener('click', function() {
if (selectedCell && currentColorPickerType) {
const cellId = selectedCell.dataset.id;
if (cellStyles[cellId]) {
if (currentColorPickerType === 'text') {
delete cellStyles[cellId].color;
selectedCell.style.color = '';
textColorPreview.style.backgroundColor = defaultTextColor;
} else {
delete cellStyles[cellId].backgroundColor;
selectedCell.style.backgroundColor = '';
fillColorPreview.style.backgroundColor = defaultFillColor;
}
saveState();
}
}
colorPicker.classList.add('hidden');
});
// Color picker cancel
document.getElementById('color-picker-cancel').addEventListener('click', function() {
colorPicker.classList.add('hidden');
});
// Clear contents button
document.getElementById('clear-contents').addEventListener('click', function() {
if (selectedCell) {
const cellId = selectedCell.dataset.id;
// Clear data and formulas but keep formatting
delete data[cellId];
delete formulas[cellId];
selectedCell.textContent = '';
// Save state
saveState();
// Update status
statusElement.textContent = "Contenido eliminado";
setTimeout(() => statusElement.textContent = "Listo", 2000);
}
});
// Clear formats button
document.getElementById('clear-formats').addEventListener('click', function() {
if (selectedCell) {
const cellId = selectedCell.dataset.id;
// Remove all formatting
delete cellStyles[cellId];
// Reset cell styles
selectedCell.style = '';
selectedCell.className = 'cell absolute bg-gray-800 border border-gray-700 p-1 overflow-hidden text-sm selected';
// Reset color previews
textColorPreview.style.backgroundColor = defaultTextColor;
fillColorPreview.style.backgroundColor = defaultFillColor;
// Reset format buttons
document.querySelectorAll('.format-button').forEach(btn => btn.classList.remove('active'));
// Save state
saveState();
// Update status
statusElement.textContent = "Formato eliminado";
setTimeout(() => statusElement.textContent = "Listo", 2000);
}
});
// File operations
document.getElementById('new-file').addEventListener('click', function() {
if (confirm('¿Crear nuevo archivo? Se perderán los cambios no guardados.')) {
// Reset all data
data = {};
formulas = {};
cellStyles = {};
cellWidths = {};
cellHeights = {};
// Clear all cells
document.querySelectorAll('.cell').forEach(cell => {
cell.textContent = '';
cell.style = '';
cell.className = 'cell absolute bg-gray-800 border border-gray-700 p-1 overflow-hidden text-sm';
});
// Reset column widths
for (let col = 0; col < COLS; col++) {
columnHeaders.children[col].style.width = `${CELL_WIDTH}px`;
document.querySelectorAll(`.cell[data-col="${col}"]`).forEach(cell => {
cell.style.width = `${CELL_WIDTH}px`;
});
}
// Reset row heights
for (let row = 0; row < ROWS; row++) {
rowNumbers.children[row + 1].style.height = `${CELL_HEIGHT}px`;
document.querySelectorAll(`.cell[data-row="${row}"]`).forEach(cell => {
cell.style.height = `${CELL_HEIGHT}px`;
});
}
// Reset grid dimensions
grid.style.width = `${COLS * CELL_WIDTH}px`;
grid.style.height = `${ROWS * CELL_HEIGHT}px`;
// Reset formula bar
formulaInput.value = '';
cellReference.textContent = 'A1';
// Reset color previews
textColorPreview.style.backgroundColor = defaultTextColor;
fillColorPreview.style.backgroundColor = defaultFillColor;
// Reset format buttons
document.querySelectorAll('.format-button').forEach(btn => btn.classList.remove('active'));
// Save state
saveState();
// Update status
statusElement.textContent = "Nuevo archivo creado";
setTimeout(() => statusElement.textContent = "Listo", 2000);
}
});
document.getElementById('save-file').addEventListener('click', function() {
// In a real app, this would save to server or download file
const excelData = {
data: data,
formulas: formulas,
cellStyles: cellStyles,
cellWidths: cellWidths,
cellHeights: cellHeights
};
console.log('Datos a guardar:', excelData);
statusElement.textContent = "Archivo guardado (consola)";
setTimeout(() => statusElement.textContent = "Listo", 2000);
});
document.getElementById('export-pdf').addEventListener('click', function() {
// In a real app, this would generate a PDF
statusElement.textContent = "Exportando a PDF...";
setTimeout(() => {
statusElement.textContent = "PDF generado (simulado)";
setTimeout(() => statusElement.textContent = "Listo", 2000);
}, 1000);
});
// Edit operations
document.getElementById('undo').addEventListener('click', undo);
document.getElementById('redo').addEventListener('click', redo);
document.getElementById('cut').addEventListener('click', cut);
document.getElementById('copy').addEventListener('click', copy);
document.getElementById('paste').addEventListener('click', paste);
let clipboard = null;
function cut() {
if (selectedCell) {
copy();
data[selectedCell.dataset.id] = '';
selectedCell.textContent = '';
saveState();
statusElement.textContent = "Texto cortado";
}
}
function copy() {
if (selectedCell) {
clipboard = {
value: data[selectedCell.dataset.id] || '',
style: cellStyles[selectedCell.dataset.id] || null
};
statusElement.textContent = "Texto copiado";
}
}
function paste() {
if (selectedCell && clipboard) {
const cellId = selectedCell.dataset.id;
data[cellId] = clipboard.value;
if (clipboard.style) {
cellStyles[cellId] = {...clipboard.style};
applyCellStyles(selectedCell, cellId);
}
selectedCell.textContent = data[cellId] || '';
saveState();
statusElement.textContent = "Texto pegado";
}
}
// Save state to history
function saveState() {
// Truncate history if we're not at the end
if (historyIndex < history.length - 1) {
history = history.slice(0, historyIndex + 1);
}
// Save current state
history.push({
data: {...data},
formulas: {...formulas},
cellStyles: {...cellStyles},
cellWidths: {...cellWidths},
cellHeights: {...cellHeights}
});
historyIndex = history.length - 1;
// Limit history size
if (history.length > 50) {
history.shift();
historyIndex--;
}
}
// Undo
function undo() {
if (historyIndex > 0) {
historyIndex--;
restoreState();
statusElement.textContent = "Deshacer";
}
}
// Redo
function redo() {
if (historyIndex < history.length - 1) {
historyIndex++;
restoreState();
statusElement.textContent = "Rehacer";
}
}
// Restore state from history
function restoreState() {
const state = history[historyIndex];
data = {...state.data};
formulas = {...state.formulas};
cellStyles = {...state.cellStyles};
cellWidths = {...state.cellWidths};
cellHeights = {...state.cellHeights};
// Update all cells
document.querySelectorAll('.cell').forEach(cell => {
const cellId = cell.dataset.id;
cell.textContent = data[cellId] || '';
applyCellStyles(cell, cellId);
});
// Update column widths
for (let col = 0; col < COLS; col++) {
const colLetter = String.fromCharCode(65 + col);
const width = cellWidths[colLetter] || CELL_WIDTH;
columnHeaders.children[col].style.width = `${width}px`;
const cells = document.querySelectorAll(`.cell[data-col="${col}"]`);
cells.forEach(cell => {
cell.style.width = `${width}px`;
});
}
// Update row heights
for (let row = 0; row < ROWS; row++) {
const height = cellHeights[row + 1] || CELL_HEIGHT;
rowNumbers.children[row + 1].style.height = `${height}px`;
const cells = document.querySelectorAll(`.cell[data-row="${row}"]`);
cells.forEach(cell => {
cell.style.height = `${height}px`;
});
}
// Update grid dimensions
updateGridDimensions();
// Update color previews
if (selectedCell) {
updateColorPreviews(selectedCell.dataset.id);
updateFormatButtons(selectedCell.dataset.id);
}
}
// Insert function button
document.getElementById('insert-function').addEventListener('click', function() {
if (selectedCell) {
formulaInput.value = '=SUM(';
formulaInput.focus();
statusElement.textContent = "Insertando función SUM";
}
});
// Sort buttons
document.getElementById('sort-asc').addEventListener('click', function() {
statusElement.textContent = "Orden ascendente (simulado)";
});
document.getElementById('sort-desc').addEventListener('click', function() {
statusElement.textContent = "Orden descendente (simulado)";
});
// Filter button
document.getElementById('filter').addEventListener('click', function() {
statusElement.textContent = "Filtro aplicado (simulado)";
});
// Chart button
document.getElementById('chart').addEventListener('click', function() {
statusElement.textContent = "Creando gráfico (simulado)";
setTimeout(() => {
alert('Gráfico creado (simulación)');
statusElement.textContent = "Listo";
}, 1000);
});
// Zoom buttons
document.getElementById('zoom-in').addEventListener('click', function() {
const currentZoom = parseInt(gridContainer.style.zoom || '100');
const newZoom = Math.min(currentZoom + 10, 200);
gridContainer.style.zoom = `${newZoom}%`;
document.querySelector('.text-pink-400').textContent = `Zoom: ${newZoom}%`;
statusElement.textContent = `Zoom: ${newZoom}%`;
});
document.getElementById('zoom-out').addEventListener('click', function() {
const currentZoom = parseInt(gridContainer.style.zoom || '100');
const newZoom = Math.max(currentZoom - 10, 50);
gridContainer.style.zoom = `${newZoom}%`;
document.querySelector('.text-pink-400').textContent = `Zoom: ${newZoom}%`;
statusElement.textContent = `Zoom: ${newZoom}%`;
});
document.getElementById('zoom-reset').addEventListener('click', function() {
gridContainer.style.zoom = '100%';
document.querySelector('.text-pink-400').textContent = 'Zoom: 100%';
statusElement.textContent = 'Zoom: 100%';
});
// Add sheet button
document.getElementById('add-sheet').addEventListener('click', function() {
const sheetButtons = document.querySelectorAll('.tab-active + button, .tab-active');
const lastSheet = sheetButtons[sheetButtons.length - 1];
const sheetNumber = parseInt(lastSheet.textContent.replace('Hoja', '')) + 1;
const newSheetButton = document.createElement('button');
newSheetButton.className = 'px-3 py-1 hover:bg-gray-700 text-sm font-medium text-gray-400';
newSheetButton.textContent = `Hoja${sheetNumber}`;
lastSheet.parentNode.insertBefore(newSheetButton, lastSheet.nextSibling);
// Add to sheets data
sheets[`Hoja${sheetNumber}`] = { data: {}, formulas: {}, cellStyles: {} };
statusElement.textContent = `Hoja${sheetNumber} añadida`;
});
// Sheet tab click
document.querySelectorAll('[class*="tab-"]').forEach(tab => {
tab.addEventListener('click', function() {
if (!this.classList.contains('tab-active')) {
// Switch sheet
document.querySelector('.tab-active').classList.remove('tab-active', 'text-blue-400');
document.querySelector('.tab-active').classList.add('text-gray-400');
this.classList.add('tab-active', 'text-blue-400');
this.classList.remove('text-gray-400');
// Update active sheet
activeSheet = this.textContent.trim();
// In a real app, we would load the sheet data
statusElement.textContent = `Cambiado a ${activeSheet}`;
}
});
});
// Detect num lock and caps lock
document.addEventListener('keydown', function(e) {
if (e.getModifierState('NumLock')) {
numLockElement.classList.remove('hidden');
} else {
numLockElement.classList.add('hidden');
}
if (e.getModifierState('CapsLock')) {
capsLockElement.classList.remove('hidden');
} else {
capsLockElement.classList.add('hidden');
}
});
// Initialize
initGrid();
// Add keyboard navigation
document.addEventListener('keydown', function(e) {
if (!selectedCell) return;
const row = parseInt(selectedCell.dataset.row);
const col = parseInt(selectedCell.dataset.col);
if (e.key === 'ArrowRight' && col < COLS - 1) {
const nextCell = document.querySelector(`.cell[data-row="${row}"][data-col="${col + 1}"]`);
nextCell.click();
} else if (e.key === 'ArrowLeft' && col > 0) {
const nextCell = document.querySelector(`.cell[data-row="${row}"][data-col="${col - 1}"]`);
nextCell.click();
} else if (e.key === 'ArrowDown' && row < ROWS - 1) {
const nextCell = document.querySelector(`.cell[data-row="${row + 1}"][data-col="${col}"]`);
nextCell.click();
} else if (e.key === 'ArrowUp' && row > 0) {
const nextCell = document.querySelector(`.cell[data-row="${row - 1}"][data-col="${col}"]`);
nextCell.click();
} else if (e.key === 'Enter') {
selectedCell.dispatchEvent(new MouseEvent('dblclick'));
} else if (e.key === 'F2') {
selectedCell.dispatchEvent(new MouseEvent('dblclick'));
} else if (e.key === 'Tab') {
e.preventDefault();
if (e.shiftKey && col > 0) {
const prevCell = document.querySelector(`.cell[data-row="${row}"][data-col="${col - 1}"]`);
prevCell.click();
} else if (col < COLS - 1) {
const nextCell = document.querySelector(`.cell[data-row="${row}"][data-col="${col + 1}"]`);
nextCell.click();
}
}
});
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=LJMolotov/my-multi-notes" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>