Files
labstream-desktop/dist/index.html
Jonathan David Florez Salomón 329ea1195e Menú de opciones ⋮ de la app (popup nativo) + preferencia de autostart
Botón ⋮ en la barra de pestañas (y clic derecho sobre ella) → menú NATIVO del
sistema, idéntico en Windows y macOS, solo del shell (la web app no se toca):

- Nueva/cerrar pestaña, recargar, acercar/alejar/tamaño real.
- "Abrir esta página en el navegador" (la pestaña activa, vía opener).
- "Iniciar con el sistema" con check: ahora la elección SE RECUERDA
  (antes el arranque re-activaba el autostart en cada apertura).
- "Buscar actualización…" manual con aviso del resultado (instalando /
  ya estás al día / sin red), además del chequeo silencioso de siempre.
- Versión visible y "Salir de Labstream OS".

El manejador de menús pasa a ser global (antes solo existía dentro del cfg de
macOS): lo comparten el menú de macOS y el popup ⋮ de ambas plataformas. El
menú de macOS gana "Buscar actualización…" y "Abrir en el navegador".

Versión 1.2.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 19:50:46 -05:00

253 líneas
8.6 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; }
</style>
</head>
<body>
<div id="bar">
<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 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");
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);
}
if (E) {
E.listen("ls-tabs", (ev) => {
estado = ev.payload || estado;
render();
});
}
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", {}));
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("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 === "=" || 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>