const EditorDeBlocos = (() => {
// Parâmetro para configurar a indentação dos blocos agrupados
const INDENTATION_SIZE_PX = 50;
// Cache global para dimensões de imagem, para evitar recarregar o new Image()
const DADOS_IMAGEM_CACHE = new Map();
// Contexto compartilhado: contém estado, elementos DOM e configurações
const context = {
config: {},
editorId: null,
controleId: null,
linhasDoEditor: [],
xBlocks: [],
proximoIdGlobal: 0,
dadosArrastados: null,
elementoArrastadoOriginalDOM: null,
ultimoTextoProcessado: '',
blocosDisponiveis: [],
containerPrincipalEl: null,
paletaBlocosEl: null,
painelEditorWrapperEl: null,
painelEditorEl: null,
xBlocksContainerEl: null,
painelParamsEl: null,
paramsTituloEl: null,
paramsCamposEl: null,
paramsOkBtn: null,
paramsCancelarBtn: null,
hamburgerBtn: null,
overlayEl: null,
callbacks: {},
listaImagens: null,
insertionMarkerEl: null,
dropOccurredInEditor: false
};
function substituirCaracteresEspeciais(texto) {
if (typeof texto !== 'string') return texto;
return texto
.replace(/,/g, '£1')
.replace(/"/g, '£2')
.replace(/'/g, '£3')
.replace(/\[/g, '£4')
.replace(/\]/g, '£5')
.replace(/\{/g, '£6')
.replace(/\}/g, '£7')
.replace(/\~/g, '£8')
.replace(/\r\n|\r|\n/g, '
');
}
// =============================== INÍCIO DO NOVO BLOCO DE CÓDIGO ===============================
/**
* Garante que uma cor hexadecimal seja escura o suficiente para ser visível em um fundo branco.
* Se a cor for muito clara, ela é escurecida na mesma matiz para um limite de visibilidade.
* @param {string} hex - A cor no formato #RRGGBB.
* @returns {string} - A cor original ou a versão escurecida.
*/
function ensureColorIsVisible(hex) {
const LUMINANCE_LIMIT = 0.85; // Limite de luminosidade (0=preto, 1=branco). 0.85 é um bom teto.
if (!hex || typeof hex !== 'string' || !hex.startsWith('#')) return hex;
// Funções auxiliares de conversão de cor
const hexToRgb = (h) => {
let r = 0, g = 0, b = 0;
if (h.length == 4) {
r = "0x" + h[1] + h[1]; g = "0x" + h[2] + h[2]; b = "0x" + h[3] + h[3];
} else if (h.length == 7) {
r = "0x" + h[1] + h[2]; g = "0x" + h[3] + h[4]; b = "0x" + h[5] + h[6];
}
return { r: +r, g: +g, b: +b };
};
const rgbToHsl = (r, g, b) => {
r /= 255; g /= 255; b /= 255;
let max = Math.max(r, g, b), min = Math.min(r, g, b);
let h = 0, s, l = (max + min) / 2;
if (max == min) {
h = s = 0;
} else {
let d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return { h, s, l };
};
const hslToRgb = (h, s, l) => {
let r, g, b;
if (s == 0) {
r = g = b = l;
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1; if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
let q = l < 0.5 ? l * (1 + s) : l + s - l * s;
let p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) };
};
const rgbToHex = (r, g, b) => '#' + [r, g, b].map(x => {
const hex = x.toString(16);
return hex.length === 1 ? '0' + hex : hex;
}).join('');
try {
const rgb = hexToRgb(hex);
const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
if (hsl.l > LUMINANCE_LIMIT) {
hsl.l = LUMINANCE_LIMIT; // Limita a luminosidade
const newRgb = hslToRgb(hsl.h, hsl.s, hsl.l);
return rgbToHex(newRgb.r, newRgb.g, newRgb.b);
}
return hex; // Retorna a cor original se não for muito clara
} catch(e) {
return hex; // Em caso de erro, retorna a cor original
}
}
// ================================ FIM DO NOVO BLOCO DE CÓDIGO =================================
function parsearTextoComEstilos(txt, corPadrao) {
const partes = [];
// Regex único e mais robusto para capturar os dois casos (com e sem cor)
const regex = /\/\*(\[(#[a-fA-F0-9]{6})\])?(.*?)\*\//g;
let ultimoIndex = 0,
match;
while ((match = regex.exec(txt)) !== null) {
// Adiciona o texto normal que veio ANTES da tag de estilo
if (match.index > ultimoIndex) {
partes.push({
text: txt.substring(ultimoIndex, match.index),
bold: false,
color: corPadrao
});
}
// Processa a tag de estilo encontrada
let corDaParte = ensureColorIsVisible(match[2] || corPadrao);
partes.push({
text: match[3], // Apenas o conteúdo
bold: true,
color: corDaParte
});
ultimoIndex = regex.lastIndex; // Atualiza o índice para depois da tag
}
// Adiciona qualquer texto normal restante no final da linha
if (ultimoIndex < txt.length) {
partes.push({
text: txt.substring(ultimoIndex),
bold: false,
color: corPadrao
});
}
return partes;
}
function reverterCaracteresEspeciais(texto) {
if (typeof texto !== 'string') return texto;
return texto
.replace(/
/g, '\n')
.replace(/£1/g, ',')
.replace(/£2/g, '"')
.replace(/£3/g, "'")
.replace(/£4/g, '[')
.replace(/£5/g, ']')
.replace(/£6/g, '{')
.replace(/£7/g, '}')
.replace(/£8/g, '~');
}
function getValorCampoNormalizado(valores, nomeDoCampoDefinido) {
if (!valores || typeof nomeDoCampoDefinido !== 'string') {
return undefined;
}
if (Object.prototype.hasOwnProperty.call(valores, nomeDoCampoDefinido)) {
return valores[nomeDoCampoDefinido];
}
const nomeBase = nomeDoCampoDefinido.replace(/<\d+>$/, '');
if (nomeBase !== nomeDoCampoDefinido) {
if (Object.prototype.hasOwnProperty.call(valores, nomeBase)) {
return valores[nomeBase];
}
}
return undefined;
}
function parseBlockName(nomeCompleto) {
if (typeof nomeCompleto !== 'string') {
return {
displayName: '',
tagName: ''
};
}
const nameRegex = /^(.*?)\[(.*?)\]$/;
const match = nomeCompleto.match(nameRegex);
if (match && match[1] && match[2]) {
return {
displayName: match[1].trim(),
tagName: match[2].trim()
};
} else {
return {
displayName: nomeCompleto,
tagName: nomeCompleto
};
}
}
function getFinalImageName(blocoObjeto, sufixoBase) {
// Converte o texto do bloco em um nome base, usando hífens (conforme seu padrão de arquivos)
const nomeBaseImagem = String(blocoObjeto.texto).replace(/\s+/g, '-').toLowerCase();
// Verifica o contexto de renderização (se está dentro de uma coluna 'E')
const dentroDeColunaE = blocoObjeto._renderContext && blocoObjeto._renderContext.dentroDeColunaE;
// Define o nome padrão do arquivo
let nomeFinal = `${nomeBaseImagem}${sufixoBase}`;
// Se estiver dentro de uma coluna, tenta encontrar a versão com 'c'
if (dentroDeColunaE) {
// Monta o nome com 'c' antes do sufixo (ex: imagem-lc.png ou imagem-lcb.png)
const nomeComC = `${nomeBaseImagem}c${sufixoBase}`;
if (context.listaImagens.has(nomeComC)) {
nomeFinal = nomeComC; // Se a versão 'c' existir, usa ela
}
}
// Retorna o nome do arquivo final se ele existir na lista, senão retorna null
return context.listaImagens.has(nomeFinal) ? nomeFinal : null;
}
function applyStyles(element, styleObject) {
if (!element || typeof element.style === 'undefined') {
return;
}
for (const property in styleObject) {
if (Object.prototype.hasOwnProperty.call(styleObject, property)) {
element.style[property] = styleObject[property];
}
}
}
function normalizarString(str) {
if (!str) return '';
return str
.toLowerCase()
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, '');
}
function parseColorString(rawColor, type = 'editor') {
if (typeof rawColor !== 'string') return rawColor;
const colors = rawColor.split('|');
if (colors.length === 2) {
return (type === 'palette' ? colors[1].trim() : colors[0].trim());
}
return colors[0].trim();
}
function getEditorColor(rawColorString) {
return parseColorString(rawColorString, 'editor');
}
function getPaletteColor(rawColorString) {
return parseColorString(rawColorString, 'palette');
}
function gerarIdUnico(prefixo = 'id_') {
return prefixo + context.proximoIdGlobal++;
}
function blocoEstaValido(bloco) {
if (bloco.groupId && bloco.groupIndex > 0) {
const cabecalhoDoGrupo = context.linhasDoEditor
.flatMap(l => l.blocos || [])
.find(b => b.groupId === bloco.groupId && b.groupIndex === 0);
if (cabecalhoDoGrupo) {
return blocoEstaValido(cabecalhoDoGrupo);
}
}
if (!bloco.camposDefinidos || bloco.camposDefinidos.length === 0) {
return true;
}
if (!bloco.valoresCampos) {
return bloco.camposDefinidos.every(campoDef =>
campoDef.nome.startsWith('~') || campoDef.nome.includes('+')
);
}
return bloco.camposDefinidos.every(campoDef => {
if (campoDef.nome.startsWith('~') || campoDef.nome.includes('+') || campoDef.nome.includes('&')) {
return true;
}
// AJUSTE CIRÚRGICO
const valor = getValorCampoNormalizado(bloco.valoresCampos, campoDef.nome);
return valor !== undefined && valor !== null && String(valor).trim() !== '';
});
}
function gerarCodigoPosicao(bloco, contexto = {}) {
const tipoBloco = bloco.tipo.charAt(0);
let posHorizontal = 'C';
let posVertical = '0';
if (contexto.emLinhaP) {
posHorizontal = contexto.ladoSlot === 'esquerda' ? 'E' : 'D';
} else if (contexto.emSlotT) {
posHorizontal = contexto.posicaoSlotT === 'inicio' ? 'E' : 'D';
posVertical = String(contexto.indiceLinhaT + 1);
}
let codigoBase = `${tipoBloco}${posHorizontal}${posVertical}`;
if ((bloco.tipo.startsWith('E') || bloco.tipo.startsWith('C')) && bloco.groupIndex === 0) {
codigoBase += bloco.isCollapsed ? 'C' : 'O';
}
return codigoBase;
}
function gerarStringParametros(bloco, codigoPosicao, parametrosOverride = null) {
let parametros = [];
if (parametrosOverride) {
parametros = [...parametrosOverride];
} else {
if (bloco.camposDefinidos && bloco.camposDefinidos.length > 0) {
const valores = bloco.valoresCampos || {};
parametros = bloco.camposDefinidos.map(campoDef => {
// AJUSTE CIRÚRGICO
const valorAtual = getValorCampoNormalizado(valores, campoDef.nome);
const valorSeguro = (valorAtual === null || valorAtual === undefined) ? '' : valorAtual;
return String(valorSeguro).replace(/,/g, '|');
});
}
}
if (codigoPosicao) {
parametros.push(codigoPosicao);
}
if (parametros.length === 0) return "";
let paramStr = parametros.join(',');
if (bloco.tipo.startsWith('E') && parametros.every(p => p === '') && parametros.length > 0 && !codigoPosicao) {
paramStr = ','.repeat(parametros.length);
} else if (bloco.tipo.startsWith('E') && parametros.length === 1 && paramStr === '' && !codigoPosicao) {
paramStr = ',';
}
return ":" + paramStr;
}
function atualizarControle() {
if (!context.controleId) return;
const controlador = document.getElementById(context.controleId);
if (!controlador) return;
let textoSaida = '';
const gerarTag = (bloco, contexto) => {
const {
tagName
} = parseBlockName(bloco.nome);
const nomeBase = normalizarString(tagName);
const codigoPosicao = gerarCodigoPosicao(bloco, contexto);
const stringParams = gerarStringParametros(bloco, codigoPosicao);
return `{${nomeBase}${stringParams}}`;
};
if (context.xBlocks.length > 0) {
textoSaida += context.xBlocks.map(b => gerarTag(b, {})).join(' ') + '\n';
}
context.linhasDoEditor.forEach(linha => {
let linhaTexto = '';
if (linha.tipoLinhaOrigem === 'S') {
linhaTexto = '
\s*( |\s*| )?\s*<\/div>\s*$/i;
const isSpacerURegex = /^\s*
\s*( |\s*| )?\s*<\/div>\s*$/i;
if (isSpacerRRegex.test(linha)) {
linhasEditor.push({
idLinha: gerarIdUnico('linha_r_'),
tipoLinhaOrigem: 'R',
blocos: []
});
return;
} else if (isSpacerSRegex.test(linha)) {
linhasEditor.push({
idLinha: gerarIdUnico('linha_s_'),
tipoLinhaOrigem: 'S',
blocos: []
});
return;
} else if (isSpacerORegex.test(linha)) {
linhasEditor.push({
idLinha: gerarIdUnico('linha_o_'),
tipoLinhaOrigem: 'O',
blocos: []
});
return;
} else if (isSpacerURegex.test(linha)) {
linhasEditor.push({
idLinha: gerarIdUnico('linha_u_'),
tipoLinhaOrigem: 'U',
blocos: []
});
return;
}
const blocosDaLinha = (linha.match(/\{([^{}]+)\}/g) || []).map(t => parseTagFn(t)).filter(Boolean);
if (blocosDaLinha.length > 0) {
const xBlocksNestaLinha = blocosDaLinha.filter(b => b.tipo === 'X');
const outrosBlocosNestaLinha = blocosDaLinha.filter(b => b.tipo !== 'X');
if (xBlocksNestaLinha.length > 0) {
xBlocks.push(...xBlocksNestaLinha);
}
if (outrosBlocosNestaLinha.length > 0) {
const primeiroBloco = outrosBlocosNestaLinha[0];
if (primeiroBloco.tipo === 'P') {
const linhaP = {
idLinha: gerarIdUnico('linha_p_'),
tipoLinhaOrigem: 'P',
slots: {
esquerda: [],
direita: []
},
blocos: []
};
outrosBlocosNestaLinha.forEach(blocoP => {
const lado = (blocoP.codigoPosicao && blocoP.codigoPosicao.charAt(1) === 'D') ? 'direita' : 'esquerda';
linhaP.slots[lado].push(blocoP);
});
linhaP.slots.direita.reverse();
linhasEditor.push(linhaP);
} else {
linhasEditor.push({
idLinha: gerarIdUnico('linha_'),
tipoLinhaOrigem: primeiroBloco.tipo.charAt(0),
blocos: [primeiroBloco]
});
}
}
}
}
function inicializarSincronizacaoReversa() {
const controlador = document.getElementById(context.controleId);
if (!controlador) return;
context.ultimoTextoProcessado = controlador.value;
carregarEditorDoTexto();
setInterval(() => {
const textoAtual = controlador.value;
if (textoAtual !== context.ultimoTextoProcessado) {
context.ultimoTextoProcessado = textoAtual;
carregarEditorDoTexto();
}
}, 500);
}
function fecharMenuHamburguer() {
if (context.paletaBlocosEl && context.paletaBlocosEl.classList.contains('paleta-visivel')) {
context.paletaBlocosEl.classList.remove('paleta-visivel');
}
}
function findBlockInContext(blockId) {
let foundBlock = null;
foundBlock = context.xBlocks.find(b => b.id === blockId);
if (foundBlock) return foundBlock;
for (const linha of context.linhasDoEditor) {
if (linha.blocos) {
foundBlock = linha.blocos.find(b => b.id === blockId);
if (foundBlock) return foundBlock;
const tBlock = linha.blocos.find(b => b.tipo.startsWith('T') && b.linhasInternas);
if (tBlock) {
for (const linhaInterna of tBlock.linhasInternas) {
if (linhaInterna.inicio) {
foundBlock = linhaInterna.inicio.find(b => b.id === blockId);
if (foundBlock) return foundBlock;
}
if (linhaInterna.fim) {
foundBlock = linhaInterna.fim.find(b => b.id === blockId);
if (foundBlock) return foundBlock;
}
}
}
}
if (linha.slots) {
if (linha.slots.esquerda) {
foundBlock = linha.slots.esquerda.find(b => b.id === blockId);
if (foundBlock) return foundBlock;
}
if (linha.slots.direita) {
foundBlock = linha.slots.direita.find(b => b.id === blockId);
if (foundBlock) return foundBlock;
}
}
}
return null;
}
function recarregarImagemDoBloco(idDoBloco) {
if (!idDoBloco) return;
setTimeout(() => {
const blocoObjeto = findBlockInContext(idDoBloco);
const elementoDOM = context.painelEditorEl.querySelector(`[data-bloco-id="${idDoBloco}"] .bloco-visor`);
// =============================== INÍCIO DO AJUSTE CIRÚRGICO ===============================
let idPaiT = null;
for (const linha of context.linhasDoEditor) {
if (linha.blocos) {
const tBlock = linha.blocos.find(b => b.tipo.startsWith('T') && b.linhasInternas);
if (tBlock) {
for (const linhaInterna of tBlock.linhasInternas) {
if (linhaInterna.inicio && linhaInterna.inicio.some(b => b.id === idDoBloco)) idPaiT = tBlock.id;
if (linhaInterna.fim && linhaInterna.fim.some(b => b.id === idDoBloco)) idPaiT = tBlock.id;
}
}
}
}
// ================================ FIM DO AJUSTE CIRÚRGICO =================================
if (blocoObjeto && elementoDOM) {
blocoObjeto.imagemUrlCache = null;
blocoObjeto.imgWidthCache = null;
blocoObjeto.imgHeightCache = null;
blocoObjeto.imagemGeradaCache = null;
blocoObjeto.textoGeradoCache = null;
blocoObjeto.alinhamentoGeradoCache = null;
blocoObjeto.tamanhoFonteGeradoCache = null;
blocoObjeto.corTextoGeradoCache = null;
blocoObjeto.corFundoGeradoCache = null;
blocoObjeto.larguraGeradoCache = null;
blocoObjeto.cacheParams4x = null;
const dentroDeColunaE = !!elementoDOM.closest(`.${context.editorId}_coluna-bloco-e`);
const contextoRenderizacao = { dentroDeColunaE: dentroDeColunaE };
carregarImagemParaBloco(elementoDOM, blocoObjeto, contextoRenderizacao);
// =============================== INÍCIO DO AJUSTE CIRÚRGICO ===============================
if (idPaiT) {
recarregarImagemDoBloco(idPaiT);
} else {
atualizarControle();
}
// ================================ FIM DO AJUSTE CIRÚRGICO =================================
}
}, 0);
}
function init(config, callbacks) {
context.config = config;
context.editorId = config.editorId;
context.controleId = config.controleId;
context.callbacks = callbacks;
let listaDeImagensArray = [];
if (Array.isArray(config.listaDeImagensDisponiveis)) {
listaDeImagensArray = config.listaDeImagensDisponiveis;
} else if (typeof config.listaDeImagensDisponiveis === 'string') {
try {
listaDeImagensArray = JSON.parse(config.listaDeImagensDisponiveis);
} catch (e) {
console.error("Falha ao decodificar a string 'listaDeImagensDisponiveis'. Verifique o formato.", e);
listaDeImagensArray = [];
}
}
if (Array.isArray(listaDeImagensArray) && listaDeImagensArray.length > 0) {
context.listaImagens = new Map(listaDeImagensArray.map(f => [f, f]));
} else {
context.listaImagens = new Map();
}
const oldWarning = document.getElementById(context.editorId + '_mobile_warning');
if (oldWarning) {
oldWarning.remove();
}
const editorContainerEl = document.getElementById(context.editorId + '_main_wrapper');
if (editorContainerEl) {
editorContainerEl.style.display = 'block';
}
try {
context.blocosDisponiveis = JSON.parse(config.jsonDisponiveisBlocos);
} catch (e) {
console.error("Erro ao parsear JSON de blocos disponíveis:", e, 'String JSON:', config.jsonDisponiveisBlocos);
context.blocosDisponiveis = [];
}
context.insertionMarkerEl = document.createElement('div');
applyStyles(context.insertionMarkerEl, {
width: '4px',
backgroundColor: '#0d6efd',
borderRadius: '2px',
alignSelf: 'stretch',
pointerEvents: 'none'
});
context.containerPrincipalEl = document.getElementById(context.editorId + '_container');
context.paletaBlocosEl = document.getElementById(context.editorId + '_paleta-puzzle');
context.painelEditorWrapperEl = document.getElementById(context.editorId + '_painel_editor_wrapper');
context.painelEditorEl = document.createElement('div');
context.painelEditorEl.id = context.editorId + '_editor';
context.painelParamsEl = document.getElementById(context.editorId + '_painel_parametros');
context.paramsTituloEl = document.getElementById(context.editorId + '_params_titulo');
context.paramsCamposEl = document.getElementById(context.editorId + '_params_campos');
context.paramsOkBtn = document.getElementById(context.editorId + '_params_ok');
context.paramsCancelarBtn = document.getElementById(context.editorId + '_params_cancelar');
if (context.painelEditorWrapperEl) {
context.painelEditorWrapperEl.appendChild(context.painelEditorEl);
} else {
console.error('Editor de Blocos: ERRO CRÍTICO - painelEditorWrapperEl não encontrado.');
return;
}
const nomeBlocoInternoClassName = context.editorId + '_nome-bloco-interno';
const styleEl = document.createElement('style');
styleEl.innerHTML = `
.bloco-renderizado { box-sizing: border-box; }
.bloco-visor { box-sizing: border-box; width: 100%; height: 100%; border: 2px solid transparent; }
.bloco-t-em-coluna .bloco-visor { display: flex; flex-direction: column; }
@keyframes carregar-fundo {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.bloco-carregando-imagem .bloco-visor {
background: linear-gradient(90deg, #f0f0f0, #e0e0e0, #f0f0f0) !important;
background-size: 200% 100% !important;
animation: carregar-fundo 1.5s infinite linear !important;
}
.bloco-grupo-cabecalho { cursor: default !important; }
.accordion-toggle {
margin-right: 8px; font-size: 0.8em; color: #555;
transition: transform 0.2s; user-select: none; cursor: pointer;
}
.dragging-original-hidden { opacity: 0.3 !important; }
.${nomeBlocoInternoClassName} { font-weight: bold; font-size: 0.85em; color: #333; flex-grow: 1;}
.bloco-invalido .bloco-visor {
border: 2px solid #e53935 !important;
}
.bloco-t-em-coluna { min-height: 250px !important; }
#${context.editorId}_main_wrapper { position: relative; }
#${context.editorId}_container { overflow: hidden; position: relative; }
.editor-toolbar {
display: flex; justify-content: space-between; align-items: center;
padding: 5px 10px; border: 1px solid #ccc; background-color: #f0f0f0;
border-bottom: none; flex-wrap: nowrap; gap: 15px;
}
.editor-switch-label-group {
display: flex; align-items: center; gap: 8px;
flex-shrink: 1; min-width: 0;
}
.editor-switch-label-group > span { flex-shrink: 0; }
.hamburger-menu {
display: none; width: 34px; height: 34px; cursor: pointer;
padding: 4px; box-sizing: border-box; z-index: 1002; flex-shrink: 0;
}
.hamburger-menu span {
display: block; width: 100%; height: 3px; background-color: #333;
margin: 5px 0; transition: all 0.3s;
}
@media (max-width: 992px) {
.hamburger-menu { display: block; }
#${context.editorId}_container { flex-direction: column; height: auto; max-height: none; }
#${context.editorId}_paleta-puzzle {
position: absolute; top: 0; right: 0; bottom: 0;
width: 280px; max-width: 80%; height: auto; max-height: none;
transform: translateX(100%); transition: transform 0.3s ease-in-out;
z-index: 1001; box-shadow: -2px 0 5px rgba(0,0,0,0.2);
flex-direction: row; flex-wrap: wrap; overflow-y: auto;
align-content: flex-start; gap: 8px;
}
#${context.editorId}_paleta-puzzle.paleta-visivel { transform: translateX(0); }
#${context.editorId}_paleta-puzzle > div[draggable="true"] { width: calc(50% - 8px); margin: 0; }
#${context.editorId}_painel_editor_wrapper { width: 100%; min-height: 500px; max-height: none; aspect-ratio: unset; }
}
`;
document.head.appendChild(styleEl);
const estilosBase = {
fontFamily: 'Arial, sans-serif',
boxSizing: 'border-box'
};
context.estilosGlobais = {
base: estilosBase,
container: { display: 'flex', flexDirection: 'row', gap: '15px', border: '1px solid #ccc', borderTop: 'none', padding: '10px', backgroundColor: 'transparent', height: 'auto', ...estilosBase, fontSize: '0.85em' },
paletaBlocos: { width: config.larguraPaleta, minWidth: config.larguraMinimaPaleta, border: '1px solid #ddd', padding: '10px', backgroundColor: '#fff', overflowY: 'auto', boxShadow: '0 0 5px rgba(0,0,0,0.1)', display: 'flex', flexDirection: 'row', flexWrap: 'wrap', gap: '4px', alignContent: 'flex-start', maxHeight: '500px' },
painelEditorWrapper: { flexGrow: '1', border: '1px solid #ddd', backgroundColor: '#fff', padding: '10px', overflowY: 'auto', overflowX: 'hidden', position: 'relative', minHeight: '400px', maxHeight: '80vh', aspectRatio: '32 / 9', width: '100%' },
xContainer: { display: 'flex', flexWrap: 'wrap', gap: '4px', marginBottom: '10px', padding: '5px', border: '1px dashed #ccc', minHeight: '42px', alignItems: 'stretch', alignContent: 'flex-start', transition: 'background-color 0.2s' },
editorLinhaFlex: { display: 'flex', flexWrap: 'nowrap', marginBottom: '4px', position: 'relative', gap: '4px', alignItems: 'stretch' },
editorLinhaGrid: { display: 'grid', gridTemplateColumns: '1fr 1fr', marginBottom: '4px', position: 'relative', gap: '4px', alignItems: 'stretch' },
blocoPaletaBase: { padding: '6px 8px', borderRadius: '4px', cursor: 'grab', fontSize: '0.7em', textAlign: 'center', boxShadow: '0 1px 3px rgba(0,0,0,0.1)', ...estilosBase, width: 'calc(50% - 2px)', wordBreak: 'break-word' },
blocoRenderizadoBase: { margin: '0', position: 'relative', ...estilosBase, transition: 'transform 0.1s, opacity 0.2s', display: 'flex', flexGrow: '1' },
blocoVisorBase: { padding: '8px', borderRadius: '4px', wordBreak: 'break-word', display: 'flex', flexDirection: 'column', justifyContent: 'center', height: '100%', width: '100%', border: '2px solid transparent' },
blocoL: { backgroundColor: getEditorColor(config.cores.L_FUNDO) },
blocoP: { backgroundColor: getEditorColor(config.cores.P_FUNDO), minWidth: '20px' },
blocoT: { backgroundColor: getEditorColor(config.cores.T_FUNDO) },
blocoS: { backgroundColor: getEditorColor(config.cores.S_FUNDO) },
blocoO: { backgroundColor: getEditorColor(config.cores.O_FUNDO || config.cores.S_FUNDO) },
blocoU: { backgroundColor: getEditorColor(config.cores.U_FUNDO || config.cores.S_FUNDO) },
blocoR: { backgroundColor: getEditorColor(config.cores.U_FUNDO || config.cores.S_FUNDO) },
blocoE: { backgroundColor: getEditorColor(config.cores.E_FUNDO) },
blocoC: { width: '100%', marginLeft: '0px', backgroundColor: getEditorColor(config.cores.C_FUNDO || '#F5DAF5'), height: '15px', flexGrow: 0, justifyContent: 'center' },
blocoX: { backgroundColor: '#E0E0E0' },
blocoEGrupoWrapper: { width: '100%', marginBottom: '4px', borderRadius: '4px', overflow: 'hidden' },
blocoEColunasContainer: { display: 'flex', flexDirection: 'row', gap: '4px', padding: '4px', backgroundColor: getEditorColor(config.cores.E_FUNDO) },
blocoEColuna: { flex: '1 1 0', minWidth: '0', minHeight: '80px', backgroundColor: 'rgba(255,255,255,0.5)', borderRadius: '3px', border: '1px dashed #e1bee7', display: 'flex', flexDirection: 'column', gap: '4px', transition: 'background-color 0.2s' },
blocoPDentroTOverride: { backgroundColor: '#ffffff', height: '100%' },
blocoRenderizadoL: { width: '100%' },
blocoRenderizadoE: { width: '100%' },
blocoRenderizadoX: { flex: '1 1 80px', minWidth: '80px', maxWidth: '180px' },
blocoRenderizadoTContainer: { width: '100%', minHeight: '150px' },
linhaInternaT: { display: 'flex', flex: '1', minHeight: '50px', alignItems: 'stretch', gap: '5px' },
// ### AJUSTE 3: LARGURA DO SLOT T (Base) ###
// Define min-width e max-width para serem iguais ao flex-basis.
// Isso força o slot a ter uma largura fixa de 33%, impedindo que ele
// encolha ou estique, resolvendo a instabilidade de tamanho.
slotT: { display: 'flex', flexWrap: 'nowrap', alignItems: 'flex-start', flexGrow: 0, flexShrink: 0, flexBasis: '33%', minWidth: '33%', maxWidth: '33%', minHeight: '40px', padding: '2px', border: '1px dashed rgba(0,0,0,0.2)', backgroundColor: getEditorColor(config.cores.SLOT_T_FUNDO), borderRadius: '3px', overflow: 'hidden', position: 'relative', gap: '2px' },
slotLinhaP: { display: 'flex', flexWrap: 'nowrap', alignItems: 'center', padding: '0', border: '1px dashed #e0e0e0', backgroundColor: 'transparent', borderRadius: '3px', gap: '4px', boxSizing: 'border-box', width: '100%', minHeight: '80px', minWidth: '0' },
slotTInicio: { justifyContent: 'flex-start' },
slotTFim: { justifyContent: 'flex-end' },
accordionToggle: { padding: '0 8px 0 4px', minWidth: '1em', textAlign: 'center' },
dragPlaceholder: { height: 'auto', minHeight: '1px', padding: '1px 0', margin: '0', backgroundColor: 'transparent', border: 'none', width: '100%', opacity: '0', transition: 'opacity 0.1s ease-out, padding 0.1s ease-out, min-height 0.1s ease-out, background-color 0.2s', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '0.75em', color: '#333', textAlign: 'center', overflow: 'hidden', lineHeight: 'normal' },
dragPlaceholderVisibleBase: { opacity: '1', border: 'none', margin: '0', backgroundColor: 'transparent', display: 'flex', alignItems: 'center', justifyContent: 'flex-start' },
blocoFantasmaEstilo: { opacity: '0.6', transform: 'scale(1)', pointerEvents: 'none', boxSizing: 'border-box', margin: '0 !important' }
};
applyStyles(context.containerPrincipalEl, context.estilosGlobais.container);
applyStyles(context.paletaBlocosEl, context.estilosGlobais.paletaBlocos);
applyStyles(context.painelEditorWrapperEl, context.estilosGlobais.painelEditorWrapper);
const mainWrapperEl = document.getElementById(context.editorId + '_main_wrapper');
const viewSwitchEl = document.getElementById(context.editorId + '_view_switch');
const editorViewEl = document.getElementById(context.editorId + '_container');
const textareaControlEl = document.getElementById(context.controleId);
if (mainWrapperEl && viewSwitchEl && editorViewEl && textareaControlEl) {
const toolbarEl = document.createElement('div');
toolbarEl.className = 'editor-toolbar';
const switchLabelGroup = document.createElement('div');
switchLabelGroup.className = 'editor-switch-label-group';
const labelTexto = document.createElement('span');
labelTexto.textContent = 'Texto\u00A0\u00A0';
const labelVisual = document.createElement('span');
labelVisual.textContent = '\u00A0\u00A0Visual';
const switchContainer = viewSwitchEl.parentElement;
switchLabelGroup.appendChild(labelTexto);
if (switchContainer) {
switchLabelGroup.appendChild(switchContainer);
}
switchLabelGroup.appendChild(labelVisual);
toolbarEl.appendChild(switchLabelGroup);
context.hamburgerBtn = document.createElement('div');
context.hamburgerBtn.className = 'hamburger-menu';
context.hamburgerBtn.innerHTML = '';
toolbarEl.appendChild(context.hamburgerBtn);
mainWrapperEl.insertBefore(toolbarEl, editorViewEl);
editorViewEl.appendChild(context.paletaBlocosEl);
const toggleMenu = () => {
context.paletaBlocosEl.classList.toggle('paleta-visivel');
};
context.hamburgerBtn.addEventListener('click', toggleMenu);
if (textareaControlEl.parentNode !== mainWrapperEl) {
mainWrapperEl.appendChild(textareaControlEl);
}
const toggleView = () => {
if (viewSwitchEl.checked) {
toolbarEl.style.display = 'flex';
editorViewEl.style.display = 'flex';
textareaControlEl.style.display = 'none';
carregarEditorDoTexto();
} else {
toolbarEl.style.display = 'flex';
editorViewEl.style.display = 'none';
textareaControlEl.style.display = 'block';
const editorHeight = editorViewEl.offsetHeight;
textareaControlEl.style.height = editorHeight > 100 ? `${editorHeight}px` : '620px';
textareaControlEl.style.width = '100%';
textareaControlEl.style.boxSizing = 'border-box';
textareaControlEl.style.border = '1px solid #ccc';
}
};
viewSwitchEl.addEventListener('change', toggleView);
toggleView();
}
if (Array.isArray(context.blocosDisponiveis)) {
context.blocosDisponiveis.forEach(bloco => {
const key = `${bloco.nome}-${bloco.tipo}`;
const divBlocoPaleta = document.createElement('div');
divBlocoPaleta.dataset.key = key;
applyStyles(divBlocoPaleta, context.estilosGlobais.blocoPaletaBase);
let corFundo = '#f0f0f0';
let corBorda = '#ccc';
const tipoBase = bloco.tipo.charAt(0);
if (tipoBase === 'L') {
corFundo = getPaletteColor(config.cores.L_FUNDO);
corBorda = getPaletteColor(config.cores.L_BORDA);
} else if (tipoBase === 'P') {
corFundo = getPaletteColor(config.cores.P_FUNDO);
corBorda = getPaletteColor(config.cores.P_BORDA);
} else if (tipoBase === 'T') {
corFundo = getPaletteColor(config.cores.T_FUNDO);
corBorda = getPaletteColor(config.cores.T_BORDA);
} else if (tipoBase === 'S') {
corFundo = getPaletteColor(config.cores.S_FUNDO);
corBorda = getPaletteColor(config.cores.S_BORDA);
} else if (tipoBase === 'O') {
corFundo = getPaletteColor(config.cores.O_FUNDO || config.cores.S_FUNDO);
corBorda = getPaletteColor(config.cores.O_BORDA || config.cores.S_BORDA);
} else if (tipoBase === 'U') {
corFundo = getPaletteColor(config.cores.U_FUNDO || config.cores.S_FUNDO);
corBorda = getPaletteColor(config.cores.U_BORDA || config.cores.S_BORDA);
} else if (tipoBase === 'R') {
corFundo = getPaletteColor(config.cores.U_FUNDO || config.cores.S_FUNDO);
corBorda = getPaletteColor(config.cores.U_BORDA || config.cores.S_BORDA);
} else if (tipoBase === 'E') {
corFundo = getPaletteColor(config.cores.E_FUNDO);
corBorda = getPaletteColor(config.cores.E_BORDA);
} else if (tipoBase === 'C') {
corFundo = getPaletteColor(config.cores.C_FUNDO || '#F5DAF5');
corBorda = getPaletteColor(config.cores.C_BORDA || '#E1BEE7');
} else if (tipoBase === 'X') {
corFundo = context.estilosGlobais.blocoX.backgroundColor;
corBorda = '#9E9E9E';
}
applyStyles(divBlocoPaleta, {
backgroundColor: corFundo,
border: `1px solid ${corBorda}`
});
divBlocoPaleta.innerHTML = parseBlockName(bloco.nome).displayName;
divBlocoPaleta.draggable = true;
divBlocoPaleta.addEventListener('dragstart', async (e) => {
fecharMenuHamburguer();
const dragData = {
...bloco,
origem: 'paleta'
};
try {
e.dataTransfer.setData('text/plain', JSON.stringify(dragData));
context.dadosArrastados = dragData;
} catch (err) {
console.error("Erro em setData ou ao setar dadosArrastados:", err, dragData);
e.preventDefault();
return;
}
applyStyles(e.target, {
opacity: '0.5'
});
const clone = await criarBlocoFantasma(dragData);
if (!clone) return;
applyStyles(clone, {
position: 'absolute',
top: '-9999px',
left: '-9999px'
});
document.body.appendChild(clone);
try {
const rect = e.target.getBoundingClientRect();
const offsetX = e.clientX - rect.left;
const offsetY = e.clientY - rect.top;
e.dataTransfer.setDragImage(clone, offsetX, offsetY);
} catch (err) {
console.warn("Paleta dragstart: setDragImage falhou:", err);
}
setTimeout(() => {
if (document.body.contains(clone)) document.body.removeChild(clone);
}, 0);
});
divBlocoPaleta.addEventListener('dragend', (e) => {
if (e.target && typeof e.target.style !== 'undefined') {
applyStyles(e.target, {
opacity: '1'
});
}
if (context.dadosArrastados && context.dadosArrastados.origem === 'paleta') {
context.dadosArrastados = null;
}
limparFantasmasAtivos();
document.querySelectorAll('[data-id-linha]').forEach(ld => {
if (ld.dataset.tipoLinha !== 'P') {
applyStyles(ld, context.estilosGlobais.editorLinhaFlex);
} else {
applyStyles(ld, context.estilosGlobais.editorLinhaGrid);
}
ld.querySelectorAll('.slot-t, .slot-linha-p').forEach(slot => {
if (slot.classList.contains('slot-t')) {
applyStyles(slot, context.estilosGlobais.slotT);
if (slot.dataset.posicaoSlotT === 'inicio') applyStyles(slot, context.estilosGlobais.slotTInicio);
if (slot.dataset.posicaoSlotT === 'fim') applyStyles(slot, context.estilosGlobais.slotTFim);
} else if (slot.classList.contains('slot-linha-p')) {
applyStyles(slot, context.estilosGlobais.slotLinhaP);
if (slot.classList.contains('slot-direita')) {
applyStyles(slot, {
justifyContent: 'flex-end'
});
} else {
applyStyles(slot, {
justifyContent: 'flex-start'
});
}
}
});
});
document.querySelectorAll(`#${context.editorId}_editor > div[data-target-linha-index]`).forEach(ph => {
const estiloInativo = { ...context.estilosGlobais.dragPlaceholder
};
if (ph.dataset.isFinal === 'true') {
if (context.linhasDoEditor.length === 0) estiloInativo.minHeight = '30px';
else estiloInativo.minHeight = '1px';
}
applyStyles(ph, estiloInativo);
ph.innerHTML = '';
});
});
context.paletaBlocosEl.appendChild(divBlocoPaleta);
});
}
context.painelEditorWrapperEl.addEventListener('dragover', (e) => {
e.preventDefault();
});
context.painelEditorWrapperEl.addEventListener('drop', (e) => {
context.dropOccurredInEditor = true;
e.preventDefault();
e.stopPropagation();
if (e.target === context.painelEditorWrapperEl || e.target === context.painelEditorEl) {
const dados = context.dadosArrastados;
if (!dados) return;
const targetIndex = dados.tipo === 'X' ? -1 : context.linhasDoEditor.length;
processarDadosTransferidos(dados, targetIndex);
}
});
if (context.containerPrincipalEl && context.paletaBlocosEl && context.painelEditorWrapperEl && context.painelEditorEl) {
inicializarSincronizacaoReversa();
} else {
console.error("Falha na inicialização do editor.");
}
}
return {
init: init,
recarregarImagemDoBloco: recarregarImagemDoBloco,
getContext: () => ({
getEditorColor: getEditorColor,
getLinhasEditor: () => context.linhasDoEditor,
getXBlocks: () => context.xBlocks,
renderizarEditor: renderizarEditor,
painelParamsEl: context.painelParamsEl,
paramsTituloEl: context.paramsTituloEl,
paramsCamposEl: context.paramsCamposEl,
paramsOkBtn: context.paramsOkBtn,
paramsCancelarBtn: context.paramsCancelarBtn,
paletaBlocosEl: context.paletaBlocosEl,
})
};
})();