Files
labstream-desktop/dist/index.html
Jonathan David Florez Salomón d8a35f5bae Atrás/adelante en la barra + reabrir pestaña + globo de no-leídos en el icono
- ATRÁS/ADELANTE por fin: botones ← → en la barra de pestañas, ⌘[ / ⌘] en
  ambas plataformas, Alt+←/→ en Windows (fuera de campos de texto) y menú
  «Historial» en macOS. Cierra el callejón sin salida: una página sin menú
  propio (p. ej. el portal de revisión) dejaba al usuario atrapado sin forma
  de volver.
- «Reabrir pestaña cerrada» (⌘/Ctrl+Shift+T, tope de 20): en la barra ⌘⇧T
  antes caía en «nueva pestaña» por no mirar el Shift.
- Globo de NO-LEÍDOS en el icono: la web app emite `ls-badge {count}` (mismo
  conteo del «(N)» del título) y el shell pinta el número en el Dock (macOS),
  un punto rojo dibujado a mano sobre el icono de la barra de tareas (Windows,
  sin assets) y el tooltip de la bandeja («— N sin leer»).

Verificado: cargo check limpio (macOS) y firmas de las APIs solo-Windows
(set_overlay_icon, set_badge_count, Image::new_owned) cotejadas contra el
código fuente de tauri 2.11.3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 07:11:37 -05:00

273 líneas
9.7 KiB
HTML
Original Blame Histórico

Este archivo contiene caracteres Unicode ambiguos

Este archivo contiene caracteres Unicode que pueden confundirse con otros caracteres. Si crees que esto es intencional, puedes ignorar esta advertencia. Usa el botón de Escape para revelarlos.

<!doctype html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Labstream OS</title>
<!--
Barra de pestañas del shell de escritorio (webview "chrome", local).
Habla con Rust por eventos (__TAURI__.event):
recibe ls-tabs {tabs:[{label,title,active}], zoom}
emite ls-ready, ls-new-tab, ls-activate, ls-close-tab, ls-reload, ls-zoom, ls-cycle, ls-activate-n
-->
<style>
:root {
--bg: #f4f4f5; /* zinc-100 */
--fg: #3f3f46; /* zinc-700 */
--muted: #a1a1aa; /* zinc-400 */
--tab-bg: transparent;
--tab-active-bg: #ffffff;
--tab-active-fg: #18181b;
--accent: #6366f1; /* indigo-500, color de Labstream */
--border: #e4e4e7; /* zinc-200 */
--hover: rgba(0, 0, 0, 0.05);
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #18181b;
--fg: #d4d4d8;
--muted: #71717a;
--tab-active-bg: #27272a;
--tab-active-fg: #fafafa;
--border: #27272a;
--hover: rgba(255, 255, 255, 0.07);
}
}
* { box-sizing: border-box; }
html, body {
margin: 0;
height: 100%;
overflow: hidden;
background: var(--bg);
color: var(--fg);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
font-size: 12.5px;
user-select: none;
-webkit-user-select: none;
cursor: default;
}
#bar {
display: flex;
align-items: stretch;
gap: 4px;
height: 40px;
padding: 5px 8px 0;
border-bottom: 1px solid var(--border);
}
#tabs {
display: flex;
align-items: stretch;
gap: 2px;
flex: 1 1 auto;
min-width: 0;
overflow-x: auto;
scrollbar-width: none;
}
#tabs::-webkit-scrollbar { display: none; }
.tab {
display: flex;
align-items: center;
gap: 6px;
flex: 0 1 200px;
min-width: 96px;
padding: 0 6px 0 12px;
border-radius: 8px 8px 0 0;
background: var(--tab-bg);
position: relative;
overflow: hidden;
}
.tab:hover { background: var(--hover); }
.tab.active {
background: var(--tab-active-bg);
color: var(--tab-active-fg);
box-shadow: 0 -1px 0 var(--border), 1px 0 0 var(--border), -1px 0 0 var(--border);
}
.tab.active::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0;
height: 2px;
background: var(--accent);
border-radius: 2px 2px 0 0;
}
.tab .titulo {
flex: 1 1 auto;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tab .cerrar {
flex: 0 0 auto;
width: 18px;
height: 18px;
display: grid;
place-items: center;
border-radius: 5px;
color: var(--muted);
font-size: 13px;
line-height: 1;
visibility: hidden;
}
.tab:hover .cerrar, .tab.active .cerrar { visibility: visible; }
.tab .cerrar:hover { background: var(--hover); color: var(--tab-active-fg); }
button {
appearance: none;
border: 0;
background: transparent;
color: var(--fg);
font: inherit;
border-radius: 7px;
cursor: default;
display: grid;
place-items: center;
}
button:hover { background: var(--hover); }
.icono {
flex: 0 0 auto;
width: 30px;
margin: 2px 0 7px;
font-size: 15px;
}
#zoom {
flex: 0 0 auto;
display: flex;
align-items: center;
gap: 1px;
margin: 2px 0 7px;
}
#zoom button { height: 100%; }
#z-menos, #z-mas { width: 26px; font-size: 14px; }
#z-pct {
min-width: 44px;
padding: 0 4px;
font-variant-numeric: tabular-nums;
color: var(--muted);
font-size: 11.5px;
}
#z-pct.activo { color: var(--accent); font-weight: 600; }
#version {
flex: 0 0 auto;
margin: 2px 0 7px;
padding: 0 7px;
color: var(--muted);
font-size: 11px;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
#version:hover { color: var(--accent); }
</style>
</head>
<body>
<div id="bar">
<button class="icono" id="atras" title="Atrás (⌘/Ctrl+[)">←</button>
<button class="icono" id="adelante" title="Adelante (⌘/Ctrl+])">→</button>
<div id="tabs" title=""></div>
<button class="icono" id="nueva" title="Nueva pestaña (⌘/Ctrl+T)">+</button>
<button class="icono" id="recargar" title="Recargar (⌘/Ctrl+R)">⟳</button>
<div id="zoom">
<button id="z-menos" title="Alejar (⌘/Ctrl+−)">−</button>
<button id="z-pct" title="Restablecer zoom (⌘/Ctrl+0)">100%</button>
<button id="z-mas" title="Acercar (⌘/Ctrl++)">+</button>
</div>
<button id="version" title="Versión de la app · clic: buscar actualización"></button>
<button class="icono" id="menu" title="Opciones de la app">⋮</button>
</div>
<script>
const T = window.__TAURI__;
const E = T && T.event;
let estado = { tabs: [], zoom: 100 };
function emitir(nombre, datos) {
try { E.emit(nombre, datos || {}); } catch (e) {}
}
const $tabs = document.getElementById("tabs");
const $pct = document.getElementById("z-pct");
const $ver = document.getElementById("version");
function render() {
$tabs.textContent = "";
for (const t of estado.tabs) {
const el = document.createElement("div");
el.className = "tab" + (t.active ? " active" : "");
el.title = t.title;
const titulo = document.createElement("span");
titulo.className = "titulo";
titulo.textContent = t.title || "Labstream OS";
el.appendChild(titulo);
const x = document.createElement("button");
x.className = "cerrar";
x.textContent = "×";
x.title = "Cerrar pestaña (⌘/Ctrl+W)";
x.addEventListener("click", (e) => {
e.stopPropagation();
emitir("ls-close-tab", { label: t.label });
});
el.appendChild(x);
el.addEventListener("click", () => emitir("ls-activate", { label: t.label }));
// Clic central sobre la pestaña = cerrarla (como en el navegador).
el.addEventListener("auxclick", (e) => {
if (e.button === 1) emitir("ls-close-tab", { label: t.label });
});
$tabs.appendChild(el);
}
const activa = $tabs.querySelector(".tab.active");
if (activa) activa.scrollIntoView({ block: "nearest", inline: "nearest" });
$pct.textContent = estado.zoom + "%";
$pct.classList.toggle("activo", estado.zoom !== 100);
$ver.textContent = estado.version ? "v" + estado.version : "";
}
if (E) {
E.listen("ls-tabs", (ev) => {
estado = ev.payload || estado;
render();
});
}
document.getElementById("atras").addEventListener("click", () => emitir("ls-nav", { dir: -1 }));
document.getElementById("adelante").addEventListener("click", () => emitir("ls-nav", { dir: 1 }));
document.getElementById("nueva").addEventListener("click", () => emitir("ls-new-tab", {}));
document.getElementById("recargar").addEventListener("click", () => emitir("ls-reload", {}));
// Menú de OPCIONES de la app (nativo, lo pinta Rust): botón ⋮ o clic derecho en la barra.
document.getElementById("menu").addEventListener("click", () => emitir("ls-menu", {}));
$ver.addEventListener("click", () => emitir("ls-check-update", {}));
document.getElementById("bar").addEventListener("contextmenu", (e) => {
e.preventDefault();
emitir("ls-menu", {});
});
document.getElementById("z-menos").addEventListener("click", () => emitir("ls-zoom", { dir: -1 }));
document.getElementById("z-mas").addEventListener("click", () => emitir("ls-zoom", { dir: 1 }));
$pct.addEventListener("click", () => emitir("ls-zoom", { dir: 0 }));
// Doble clic en el hueco libre de la barra = pestaña nueva.
$tabs.addEventListener("dblclick", (e) => {
if (e.target === $tabs) emitir("ls-new-tab", {});
});
// Atajos también cuando el foco está en la barra.
addEventListener("keydown", (e) => {
const mod = e.metaKey || e.ctrlKey;
if (!mod) return;
const k = e.key;
if (k === "t" || k === "T") { e.preventDefault(); emitir(e.shiftKey ? "ls-reopen" : "ls-new-tab", {}); }
else if (k === "w" || k === "W") { e.preventDefault(); emitir("ls-close-tab", {}); }
else if ((k === "r" || k === "R") && !e.shiftKey) { e.preventDefault(); emitir("ls-reload", {}); }
else if (k === "[") { e.preventDefault(); emitir("ls-nav", { dir: -1 }); }
else if (k === "]") { e.preventDefault(); emitir("ls-nav", { dir: 1 }); }
else if (k === "=" || k === "+") { e.preventDefault(); emitir("ls-zoom", { dir: 1 }); }
else if (k === "-") { e.preventDefault(); emitir("ls-zoom", { dir: -1 }); }
else if (k === "0") { e.preventDefault(); emitir("ls-zoom", { dir: 0 }); }
else if (k === "Tab") { e.preventDefault(); emitir("ls-cycle", { dir: e.shiftKey ? -1 : 1 }); }
else if (k >= "1" && k <= "9") { e.preventDefault(); emitir("ls-activate-n", { n: +k }); }
});
emitir("ls-ready", {});
</script>
</body>
</html>