// ==UserScript==
// @name DealerBarraDeTarefas
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Inclui uma barra de tarefas no DealerNet com as janelas que estão abertas, te permitindo alternar entre elas simplesmente clicando no botão correspondente, sem ter que minimizar nada.
// @author Igor Lima
// @match http*://*.dealernetworkflow.com.br/Portal/default.html
// @require https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.15.6/Sortable.js
// @grant GM_addStyle
// @license MIT
// ==/UserScript==
/*
Este código foi gerado por um modelo de IA. Embora tenha sido projetado para ser funcional,
pode ser necessário realizar uma revisão, testes ou modificações para atender às suas necessidades específicas.
Verifique o código quanto à correção e adequação antes de utilizá-lo em ambientes de produção.
*/
(function() {
'use strict';
// Configurações principais
const ALTURA_BARRA = 32; // Altura da barra de tarefas em pixels
// Gerenciamento de estado
const registroJanelas = new Map(); // Armazena informações de todas as janelas ativas
const janelasMinimizadas = new Map(); // Armazena posições das janelas minimizadas
// Estilos do sistema
GM_addStyle(`
/* Barra de tarefas */
#taskbar {
display: flex;
flex-wrap: nowrap;
gap: 5px;
width: calc(100vw - 4px);
overflow-x: auto;
white-space: nowrap;
padding: 5px 0px 0px 0px;
margin: 0;
-webkit-overflow-scrolling: touch;
scrollbar-width: thin;
scrollbar-color: #007bff #ededed;
font: normal 11px arial,tahoma,verdana,helvetica;
}
/* Personalização da barra de rolagem */
#taskbar::-webkit-scrollbar {
height: 1px;
}
#taskbar::-webkit-scrollbar-track {
background: #ededed;
border-radius: 4px;
}
#taskbar::-webkit-scrollbar-thumb {
background-color: #007bff;
border-radius: 4px;
}
#taskbar.has-scrollbar {
padding-top: 0;
}
/* Botões da barra de tarefas */
#taskbar button {
flex-shrink: 0;
padding: 5px 5px;
margin: 0;
border: 1px solid #ccc;
border-radius: 0px;
background: #fff;
cursor: pointer;
font-size: 12px;
white-space: nowrap;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
transition: background-color 0.2s, color 0.2s, opacity 0.2s;
}
/* Estado minimizado */
.minimized-window {
opacity: 0.5;
background-color: #f0f0f0 !important;
color: #666 !important;
}
/* Menu de contexto */
#window-context-menu {
position: fixed;
background: rgba(255,255,255,0.5);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
border: 1px solid #ccc;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
z-index: 10000;
padding: 5px 0;
display: none;
font: normal 11px arial,tahoma,verdana,helvetica;
}
#window-context-menu .context-menu-title {
color: #888;
font-size: 10px;
padding: 5px 15px;
border-bottom: 1px solid #eee;
}
#window-context-menu div:not(.context-menu-title) {
padding: 5px 15px;
cursor: pointer;
transition: background-color 0.2s;
}
#window-context-menu div:not(.context-menu-title):hover {
background-color: rgba(255,255,255,0.9);
}
/* Estilos para ordenação */
.sortable-drag {
opacity: 1.0;
}
.sortable-ghost {
opacity: 0.5;
}
.sortable-chosen {
opacity: 0.5;
}
`);
// Criação do menu de contexto
function criarMenuContexto() {
const menu = document.createElement('div');
menu.id = 'window-context-menu';
menu.innerHTML = `
<div class="context-menu-title"></div>
<div data-action="minimize">Minimizar</div>
<div data-action="close">Fechar</div>
`;
document.body.appendChild(menu);
return menu;
}
// Exibe o menu de contexto na posição correta
function exibirMenuContexto(evento, idJanela) {
evento.preventDefault();
const menu = document.getElementById('window-context-menu');
const tituloElemento = menu.querySelector('.context-menu-title');
// Define o título da janela no menu
const infoJanela = registroJanelas.get(idJanela);
tituloElemento.textContent = infoJanela ? infoJanela.title : 'Janela Desconhecida';
// Torna o menu visível temporariamente para obter suas dimensões
menu.style.visibility = 'hidden';
menu.style.display = 'block';
// Calcula a posição do menu considerando os limites da tela
const larguraMenu = menu.offsetWidth;
const alturaMenu = menu.offsetHeight;
const larguraTela = window.innerWidth;
const alturaTela = window.innerHeight;
// Posição horizontal - centraliza o menu em relação ao clique
let esquerda = evento.pageX - (larguraMenu / 2);
// Ajusta se estiver fora dos limites horizontais
if (esquerda < 0) {
esquerda = 0;
} else if (esquerda + larguraMenu > larguraTela) {
esquerda = larguraTela - larguraMenu;
}
// Posição vertical - posiciona o menu acima do cursor
let topo = evento.pageY - alturaMenu;
// Se não houver espaço suficiente acima, posiciona abaixo do cursor
if (topo < 0) {
topo = evento.pageY;
}
menu.style.left = `${esquerda}px`;
menu.style.top = `${topo}px`;
menu.dataset.windowId = idJanela;
// Torna o menu visível
menu.style.visibility = 'visible';
let timeoutId;
let isMouseOver = false;
// Função para fechar o menu
const fecharMenu = () => {
menu.style.display = 'none';
menu.removeEventListener('mouseenter', onMouseEnter);
menu.removeEventListener('mouseleave', onMouseLeave);
document.removeEventListener('click', aoClicarFora, true);
if (timeoutId) {
clearTimeout(timeoutId);
}
};
// Handlers do mouse
const onMouseEnter = () => {
isMouseOver = true;
if (timeoutId) {
clearTimeout(timeoutId);
}
};
const onMouseLeave = () => {
isMouseOver = false;
timeoutId = setTimeout(() => {
if (!isMouseOver) {
fecharMenu();
}
}, 200);
};
// Handler de clique fora - usando capture phase
const aoClicarFora = (e) => {
if (!menu.contains(e.target) && e.target !== menu) {
e.stopPropagation();
fecharMenu();
}
};
document.removeEventListener('click', aoClicarFora, true);
// Adiciona os novos listeners
menu.addEventListener('mouseenter', onMouseEnter);
menu.addEventListener('mouseleave', onMouseLeave);
// Adiciona o listener de clique com capture phase
setTimeout(() => {
document.addEventListener('click', aoClicarFora, true);
}, 0);
// Inicia o timeout inicial
timeoutId = setTimeout(() => {
if (!isMouseOver) {
fecharMenu();
}
}, 5000);
}
// Gerencia as ações do menu de contexto
function configurarAcoesMenuContexto(menu) {
menu.addEventListener('click', (e) => {
const acao = e.target.dataset.action;
const idJanela = menu.dataset.windowId;
if (!idJanela) return;
const infoJanela = registroJanelas.get(idJanela);
if (!infoJanela) return;
menu.style.display = 'none';
switch(acao) {
case 'minimize':
minimizarJanela(idJanela);
break;
case 'close':
fecharJanela(idJanela);
break;
}
});
}
// Inicializa o menu de contexto
const menuContexto = criarMenuContexto();
configurarAcoesMenuContexto(menuContexto);
// Funções auxiliares do menu de contexto
function fecharJanela(idJanela) {
const infoJanela = registroJanelas.get(idJanela);
if (infoJanela) {
const botaoFechar = infoJanela.element.querySelector('.x-tool-close');
if (botaoFechar) {
botaoFechar.click();
}
}
}
// Funções de gerenciamento de janelas
function minimizarJanela(idJanela) {
const infoJanela = registroJanelas.get(idJanela);
if (!infoJanela) return;
const elemento = infoJanela.element;
salvarPosicaoJanela(idJanela);
// Guarda posição original se não estiver maximizada
if (!elemento.classList.contains('x-window-maximized')) {
janelasMinimizadas.set(idJanela, {
top: elemento.style.top,
left: elemento.style.left
});
}
// Esconde a janela
elemento.style.visibility = 'hidden';
elemento.style.top = '-10000px';
elemento.style.left = '-10000px';
elemento.style.zIndex = '0';
// Atualiza aparência do botão
const botao = document.querySelector(`[data-window-id="${idJanela}"]`);
if (botao) {
botao.classList.add('minimized-window');
}
}
function restaurarJanela(idJanela) {
const infoJanela = registroJanelas.get(idJanela);
if (!infoJanela) return;
const elemento = infoJanela.element;
const posicaoSalva = janelasMinimizadas.get(idJanela);
elemento.style.visibility = 'visible';
// Define o z-index como o maior + 10 ao restaurar
const maiorZ = obterMaiorZIndex();
elemento.style.zIndex = maiorZ + 1;
infoJanela.zIndex = maiorZ + 1;
if (!elemento.classList.contains('x-window-maximized') && posicaoSalva) {
elemento.style.top = posicaoSalva.top;
elemento.style.left = posicaoSalva.left;
janelasMinimizadas.delete(idJanela);
} else {
elemento.style.top = '0';
elemento.style.left = '0';
}
const botao = document.querySelector(`[data-window-id="${idJanela}"]`);
if (botao) {
botao.classList.remove('minimized-window');
}
// Atualiza o estado dos botões para refletir a nova janela ativa
atualizarEstadoBotoes(document.getElementById('taskbar'));
}
function minimizarTodas() {
registroJanelas.forEach((info, idJanela) => {
minimizarJanela(idJanela);
});
}
function salvarPosicaoJanela(idJanela) {
const infoJanela = registroJanelas.get(idJanela);
if (infoJanela && !infoJanela.element.classList.contains('x-window-maximized')) {
janelasMinimizadas.set(idJanela, {
top: infoJanela.element.style.top,
left: infoJanela.element.style.left
});
}
}
function obterJanelaAtiva() {
let janelaAtiva = null;
let maiorZ = -1;
registroJanelas.forEach((info, id) => {
const zIndex = parseInt(info.zIndex) || 0;
if (zIndex > maiorZ) {
maiorZ = zIndex;
janelaAtiva = id;
}
});
return janelaAtiva;
}
function obterMaiorZIndex() {
let maior = 0;
registroJanelas.forEach((info) => {
const zIndex = parseInt(info.zIndex) || 0;
if (zIndex > maior) maior = zIndex;
});
return maior;
}
// Ajustes de altura dos elementos
function ajustarAlturaCorpoJanelas(reduzir = true) {
const ajuste = reduzir ? -ALTURA_BARRA + 4 : ALTURA_BARRA - 4;
document.querySelectorAll('[id^="W5Window_"]:not([id$="-rzproxy"]) .x-window-body').forEach(corpo => {
const alturaAtual = parseInt(window.getComputedStyle(corpo).height);
corpo.style.height = `${alturaAtual + ajuste}px`;
});
}
function ajustarAlturaPai(reduzir = true) {
const elementoPai = document.querySelector('.x-panel-bwrap');
if (!elementoPai) return;
const elemento = elementoPai.querySelector('.x-panel-body.x-panel-body-noheader');
if (!elemento) return;
const alturaAtual = parseInt(elemento.style.height, 10);
if (reduzir) {
elemento.style.height = `${alturaAtual - ALTURA_BARRA + 4}px`;
ajustarAlturaCorpoJanelas(true);
} else {
elemento.style.height = `${alturaAtual + ALTURA_BARRA - 4}px`;
ajustarAlturaCorpoJanelas(false);
}
}
// Funções de registro e controle de janelas
function processarJanela(elemento, containerBotoes) {
const idJanela = elemento.id;
// Ignora janelas que não devem ser processadas
if (idJanela.includes('-rzproxy')) return;
if (!idJanela.match(/^W5Window_\d+$/)) return;
const zIndex = window.getComputedStyle(elemento).zIndex;
const elementoTitulo = elemento.querySelector('.x-window-header-text');
const titulo = elementoTitulo ? elementoTitulo.textContent : 'Sem título';
registroJanelas.set(idJanela, {
element: elemento,
zIndex: zIndex,
title: titulo
});
// Substitui o botão de minimizar original
substituirBotaoMinimizar(elemento, idJanela);
criarOuAtualizarBotao(idJanela, titulo, elemento, containerBotoes);
}
function criarOuAtualizarBotao(idJanela, titulo, elemento, containerBotoes) {
let botao = containerBotoes.querySelector(`[data-window-id="${idJanela}"]`);
if (!botao) {
botao = document.createElement('button');
botao.setAttribute('data-window-id', idJanela);
containerBotoes.appendChild(botao);
// Evento de clique principal
botao.addEventListener('click', () => {
if (botao.classList.contains('minimized-window')) {
restaurarJanela(idJanela);
} else {
const maiorZ = obterMaiorZIndex();
elemento.style.zIndex = maiorZ + 1;
registroJanelas.get(idJanela).zIndex = maiorZ + 1;
atualizarEstadoBotoes(containerBotoes);
}
});
// Evento de clique do botão do meio (fechar)
botao.addEventListener('mouseup', (e) => {
if (e.button === 1) {
e.preventDefault();
fecharJanela(idJanela);
}
});
// Menu de contexto no clique direito
botao.addEventListener('contextmenu', (e) => {
exibirMenuContexto(e, idJanela);
});
}
botao.textContent = titulo;
atualizarEstadoBotoes(containerBotoes);
}
function atualizarEstadoBotoes(containerBotoes) {
const janelaAtiva = obterJanelaAtiva();
containerBotoes.querySelectorAll('button').forEach(botao => {
const estaAtiva = botao.getAttribute('data-window-id') === janelaAtiva;
botao.style.backgroundColor = estaAtiva ? '#007bff' : '#fff';
botao.style.color = estaAtiva ? '#fff' : '#000';
});
}
function substituirBotaoMinimizar(elementoJanela, idJanela) {
const botaoMinimizar = elementoJanela.querySelector('.x-tool-minimize');
if (botaoMinimizar) {
// Remove eventos do botão original
const clone = botaoMinimizar.cloneNode(true);
botaoMinimizar.parentNode.replaceChild(clone, botaoMinimizar);
// Adiciona novo comportamento
clone.addEventListener('click', (e) => {
e.stopPropagation();
minimizarJanela(idJanela);
});
}
}
function substituirBotaoMinimizarTodas() {
const botaoOriginal = document.querySelector('.minimizeAll');
if (botaoOriginal) {
const clone = botaoOriginal.cloneNode(true);
botaoOriginal.parentNode.replaceChild(clone, botaoOriginal);
clone.addEventListener('click', (e) => {
e.stopPropagation();
minimizarTodas();
});
}
}
function verificarJanelasEAtualizarBarra(linhaBotoes, barraInferior) {
const temJanelas = registroJanelas.size > 0;
if (!temJanelas) {
linhaBotoes.style.display = 'none';
barraInferior.style.height = `${parseInt(barraInferior.style.height) - ALTURA_BARRA}px`;
barraInferior.style.top = `${parseInt(barraInferior.style.top) + ALTURA_BARRA}px`;
ajustarAlturaPai(false);
} else if (linhaBotoes.style.display === 'none') {
linhaBotoes.style.display = '';
barraInferior.style.height = `${parseInt(barraInferior.style.height) + ALTURA_BARRA}px`;
barraInferior.style.top = `${parseInt(barraInferior.style.top) - ALTURA_BARRA}px`;
ajustarAlturaPai(true);
}
}
// Funções de inicialização e setup
function encontrarBarraInferior() {
const barras = document.querySelectorAll('div.x-toolbar.x-border-panel');
return Array.from(barras).find(barra => {
const topo = parseInt(barra.style.top);
const outrasBarras = Array.from(barras).filter(b => b !== barra);
return outrasBarras.every(outra => parseInt(outra.style.top) < topo);
});
}
function criarLinhaBarraTarefas() {
const tr = document.createElement('tr');
tr.className = 'window-switcher-row';
const td = document.createElement('td');
td.colSpan = 2;
td.style.cssText = 'background: #ededed; padding: 0px;';
const containerBotoes = document.createElement('div');
containerBotoes.id = 'taskbar';
td.appendChild(containerBotoes);
tr.appendChild(td);
return tr;
}
function configurarScrollBarraTarefas(containerBotoes) {
function verificarBarraRolagem() {
if (containerBotoes.scrollWidth > containerBotoes.clientWidth) {
containerBotoes.classList.add('has-scrollbar');
} else {
containerBotoes.classList.remove('has-scrollbar');
}
}
// Rolagem horizontal com roda do mouse
containerBotoes.addEventListener('wheel', function(evento) {
if (evento.deltaY !== 0) {
containerBotoes.scrollLeft += evento.deltaY;
evento.preventDefault();
}
});
// Previne autoscroll do botão do meio
containerBotoes.addEventListener('mousedown', function(evento) {
if (evento.button === 1) {
evento.preventDefault();
}
});
return verificarBarraRolagem;
}
function configurarSortable(containerBotoes) {
new Sortable(containerBotoes, {
animation: 150,
ghostClass: 'sortable-ghost',
dragClass: 'sortable-drag',
chosenClass: "sortable-chosen",
forceFallback: false,
fallbackClass: "sortable-fallback",
removeCloneOnHide: false,
direction: 'horizontal',
dragoverBubble: true,
});
}
function configurarObservadores(containerBotoes, linhaBotoes, barraInferior, verificarBarraRolagem) {
// Observer para adições/remoções de janelas
const observadorEstrutura = new MutationObserver((mutacoes) => {
let precisaAtualizar = false;
for (const mutacao of mutacoes) {
for (const node of mutacao.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.id && node.id.match(/^W5Window_\d+$/)) {
processarJanela(node, containerBotoes);
verificarJanelasEAtualizarBarra(linhaBotoes, barraInferior);
precisaAtualizar = true;
}
const janelas = node.querySelectorAll('[id^="W5Window_"]');
if (janelas.length > 0) {
janelas.forEach(janela => processarJanela(janela, containerBotoes));
verificarJanelasEAtualizarBarra(linhaBotoes, barraInferior);
precisaAtualizar = true;
}
if (node.querySelector('.minimizeAll')) {
substituirBotaoMinimizarTodas();
}
}
}
for (const node of mutacao.removedNodes) {
if (node.nodeType === Node.ELEMENT_NODE && node.id && node.id.match(/^W5Window_\d+$/)) {
const botao = containerBotoes.querySelector(`[data-window-id="${node.id}"]`);
if (botao) botao.remove();
registroJanelas.delete(node.id);
verificarJanelasEAtualizarBarra(linhaBotoes, barraInferior);
precisaAtualizar = true;
}
}
}
if (precisaAtualizar) {
atualizarEstadoBotoes(containerBotoes);
verificarBarraRolagem();
}
});
// Observer para mudanças de z-index
const observadorEstilo = new MutationObserver((mutacoes) => {
mutacoes.forEach((mutacao) => {
if (mutacao.type === 'attributes' && mutacao.attributeName === 'style') {
const elemento = mutacao.target;
if (elemento.id && elemento.id.match(/^W5Window_\d+$/)) {
const zIndex = window.getComputedStyle(elemento).zIndex;
const infoJanela = registroJanelas.get(elemento.id);
if (infoJanela && infoJanela.zIndex !== zIndex) {
infoJanela.zIndex = zIndex;
atualizarEstadoBotoes(containerBotoes);
}
}
}
});
});
return { observadorEstrutura, observadorEstilo };
}
function inicializarGerenciador() {
const barraInferior = encontrarBarraInferior();
if (!barraInferior) {
console.log('Barra inferior não encontrada, tentando novamente...');
setTimeout(inicializarGerenciador, 500);
return;
}
// Ajusta altura da barra inferior
const alturaAtual = parseInt(barraInferior.style.height);
const topoAtual = parseInt(barraInferior.style.top);
barraInferior.style.height = `${alturaAtual + ALTURA_BARRA}px`;
barraInferior.style.top = `${topoAtual - ALTURA_BARRA}px`;
// Cria e insere a barra de tarefas
const tabela = barraInferior.querySelector('table.x-toolbar-ct tbody');
const linhaBotoes = criarLinhaBarraTarefas();
tabela.insertBefore(linhaBotoes, tabela.firstChild);
ajustarAlturaPai(true);
const containerBotoes = linhaBotoes.querySelector('#taskbar');
const verificarBarraRolagem = configurarScrollBarraTarefas(containerBotoes);
configurarSortable(containerBotoes);
const { observadorEstrutura, observadorEstilo } = configurarObservadores(
containerBotoes,
linhaBotoes,
barraInferior,
verificarBarraRolagem
);
// Inicia observação
observadorEstrutura.observe(document.body, {
childList: true,
subtree: true
});
observadorEstilo.observe(document.body, {
attributes: true,
attributeFilter: ['style'],
subtree: true
});
// Verificação inicial
document.querySelectorAll('[id^="W5Window_"]').forEach(janela => {
processarJanela(janela, containerBotoes);
});
substituirBotaoMinimizarTodas();
verificarJanelasEAtualizarBarra(linhaBotoes, barraInferior);
// Adiciona listener para resize
window.addEventListener('resize', verificarBarraRolagem);
}
// Inicializa o sistema
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', inicializarGerenciador);
} else {
inicializarGerenciador();
}
})();