Comparar commits
4 Commits
| Autor | SHA1 | Fecha | |
|---|---|---|---|
|
|
29f0b47371 | ||
|
|
b18c155b89 | ||
|
|
424f401ad6 | ||
|
|
f6eea75404 |
69
dist/index.html
vendido
69
dist/index.html
vendido
@@ -7,7 +7,8 @@
|
||||
<!--
|
||||
Barra de pestañas del shell de escritorio (webview "chrome", local).
|
||||
Habla con Rust por eventos (__TAURI__.event):
|
||||
recibe ls-tabs {tabs:[{label,title,url,loading,active}], zoom, version, mac}
|
||||
recibe ls-tabs {tabs:[{label,title,url,loading,active,color}], zoom, version,
|
||||
mac, win, maximized, dark}
|
||||
emite ls-ready, ls-new-tab, ls-activate, ls-close-tab, ls-reload, ls-zoom, ls-cycle,
|
||||
ls-activate-n, ls-nav, ls-menu, ls-check-update, ls-tab-menu, ls-reorder
|
||||
En macOS la ventana lleva el título en OVERLAY: esta barra ES la fila del título, así que
|
||||
@@ -15,33 +16,60 @@
|
||||
(`data-tauri-drag-region`). En Windows hay título nativo aparte y no se reserva nada.
|
||||
-->
|
||||
<style>
|
||||
/* Paleta estilo Chrome: la barra es una FRANJA más oscura y la pestaña activa es una
|
||||
tarjeta del color del contenido que se funde con él (sin línea debajo). */
|
||||
/* La barra se pinta del color de la APP, no del gris de Chrome: blanca en tema claro y
|
||||
negra en oscuro, para que la ventana se lea como una sola pieza y no como una franja
|
||||
de navegador pegada encima.
|
||||
El truco de Chrome se conserva igual —la pestaña activa es una tarjeta del color del
|
||||
CONTENIDO que se funde con él—, solo que ahora el contraste va al revés en oscuro: la
|
||||
franja es más negra que el contenido, así que la activa destaca por ser más clara. La
|
||||
diferencia es sutil a propósito; quien la marca de verdad es el borde inferior, que la
|
||||
pestaña activa tapa con su propio fondo. */
|
||||
:root {
|
||||
--bg: #dee1e6; /* franja de pestañas (Chrome claro) */
|
||||
--bg: #f4f5f7; /* franja: casi blanca, del tono de la app en claro */
|
||||
--fg: #45474a;
|
||||
--muted: #5f6368;
|
||||
--tab-active-bg: #ffffff;
|
||||
--tab-active-bg: #ffffff; /* mismo fondo que la app → pestaña fundida con el contenido */
|
||||
--tab-active-fg: #202124;
|
||||
--accent: #6366f1; /* indigo-500, color de Labstream */
|
||||
--border: #c9ccd1; /* línea bajo la barra (la tapa la pestaña activa) */
|
||||
--hover: rgba(255, 255, 255, 0.55); /* píldora de hover en pestañas inactivas */
|
||||
--accent: #F47A20; /* NARANJA Labstream (el índigo de antes no era de la marca) */
|
||||
--border: #dcdee1; /* línea bajo la barra (la tapa la pestaña activa) */
|
||||
/* Sobre una franja casi blanca, un hover blanco no se vería: aquí oscurece. */
|
||||
--hover: rgba(0, 0, 0, 0.06);
|
||||
--btn-hover: rgba(0, 0, 0, 0.08);
|
||||
--sep: rgba(0, 0, 0, 0.22); /* separador fino entre pestañas inactivas */
|
||||
}
|
||||
/* ── Quién decide el tema ──
|
||||
La APP, no Windows: la web elige su modo con su propio botón (clase `dark` en su
|
||||
<html>, defaultTheme claro, sin seguir al sistema), así que obedecer aquí al
|
||||
prefers-color-scheme del sistema pintaba la barra negra sobre una app clara — el
|
||||
bug de siempre. El script inyectado reporta el tema real, Rust lo reenvía en
|
||||
`ls-tabs` (`dark`) y este documento se pone la clase `oscuro` o `claro`.
|
||||
El bloque de medios queda SOLO como arranque en frío (primer arranque, sin
|
||||
estado.json y sin reporte aún): en cuanto hay clase, la clase manda.
|
||||
OJO: los valores oscuros van DOS veces (media query y .oscuro) — cambiarlos a la par. */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: #202124; /* franja (Chrome oscuro) */
|
||||
:root:not(.claro) {
|
||||
--bg: #0d0d0f; /* franja: negra, más oscura que el contenido */
|
||||
--fg: #bdc1c6;
|
||||
--muted: #80868b;
|
||||
--tab-active-bg: #18181b; /* mismo fondo que la app → pestaña fundida con el contenido */
|
||||
--tab-active-fg: #e8eaed;
|
||||
--border: #101013;
|
||||
--border: #26262a;
|
||||
--hover: rgba(255, 255, 255, 0.08);
|
||||
--btn-hover: rgba(255, 255, 255, 0.1);
|
||||
--sep: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
}
|
||||
:root.oscuro {
|
||||
--bg: #0d0d0f;
|
||||
--fg: #bdc1c6;
|
||||
--muted: #80868b;
|
||||
--tab-active-bg: #18181b;
|
||||
--tab-active-fg: #e8eaed;
|
||||
--border: #26262a;
|
||||
--hover: rgba(255, 255, 255, 0.08);
|
||||
--btn-hover: rgba(255, 255, 255, 0.1);
|
||||
--sep: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
html, body {
|
||||
margin: 0;
|
||||
@@ -81,10 +109,13 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
/* Elástico como en Chrome: se reparten el ancho y se encogen al abrir más. */
|
||||
/* Elástico como en Chrome: se reparten el ancho y se encogen al abrir más. El tope
|
||||
sube a 280 px porque los títulos ahora traen nombre real y sección
|
||||
(«Hotjamin Orgánicos · Archivos»), y a 240 se cortaban de más. Con muchas pestañas
|
||||
abiertas da igual: mandan el reparto y el modo compacto. */
|
||||
flex: 1 1 220px;
|
||||
min-width: 42px;
|
||||
max-width: 240px;
|
||||
max-width: 280px;
|
||||
height: 30px;
|
||||
margin-bottom: 5px;
|
||||
padding: 0 6px 0 10px;
|
||||
@@ -158,9 +189,11 @@
|
||||
.tab[data-color]:not(.active)::before { display: none; }
|
||||
.tab[data-color].active { box-shadow: inset 0 3px 0 rgb(var(--tc)); }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.tab[data-color]:not(.active) { background: rgba(var(--tc), 0.36); }
|
||||
.tab[data-color]:not(.active):hover { background: rgba(var(--tc), 0.5); }
|
||||
:root:not(.claro) .tab[data-color]:not(.active) { background: rgba(var(--tc), 0.36); }
|
||||
:root:not(.claro) .tab[data-color]:not(.active):hover { background: rgba(var(--tc), 0.5); }
|
||||
}
|
||||
:root.oscuro .tab[data-color]:not(.active) { background: rgba(var(--tc), 0.36); }
|
||||
:root.oscuro .tab[data-color]:not(.active):hover { background: rgba(var(--tc), 0.5); }
|
||||
/* Reordenar arrastrando: NO se usa drag&drop de HTML5 (en el webview de la barra en
|
||||
macOS los eventos no llegan), sino eventos de puntero. Se apaga el arrastre nativo de
|
||||
WebKit para que no salga la imagen fantasma al mover una pestaña. */
|
||||
@@ -498,6 +531,12 @@
|
||||
$pct.textContent = estado.zoom + "%";
|
||||
$pct.classList.toggle("activo", estado.zoom !== 100);
|
||||
$ver.textContent = estado.version ? "v" + estado.version : "";
|
||||
// Tema: manda el de la APP (payload `dark`). Mientras Rust no sepa nada (null),
|
||||
// no se pone clase y siguen las media queries del sistema como arranque en frío.
|
||||
if (typeof estado.dark === "boolean") {
|
||||
document.documentElement.classList.toggle("oscuro", estado.dark);
|
||||
document.documentElement.classList.toggle("claro", !estado.dark);
|
||||
}
|
||||
// En macOS la barra ES la fila del título: hay que dejar libre el semáforo.
|
||||
document.body.classList.toggle("mac", !!estado.mac);
|
||||
// En Windows también ES la fila del título, pero ahí los botones de ventana los
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "labstream-desktop",
|
||||
"version": "1.6.0",
|
||||
"version": "1.7.0",
|
||||
"private": true,
|
||||
"description": "App de escritorio (Windows) que envuelve Labstream OS — cliente del servidor del NAS.",
|
||||
"scripts": {
|
||||
|
||||
2
src-tauri/Cargo.lock
generado
2
src-tauri/Cargo.lock
generado
@@ -2062,7 +2062,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "labstream-desktop"
|
||||
version = "1.6.0"
|
||||
version = "1.7.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "labstream-desktop"
|
||||
version = "1.6.0"
|
||||
version = "1.7.0"
|
||||
description = "Cliente de escritorio de Labstream OS"
|
||||
authors = ["Labstream SAS"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -97,11 +97,15 @@ struct Shell {
|
||||
// Último estado maximizado conocido. Solo sirve para no repintar la barra en CADA
|
||||
// evento de redimensión: únicamente cuando el icono ▢/❐ tiene que cambiar.
|
||||
maximized: bool,
|
||||
// Tema REAL de la app (la clase `dark` de <html>, reportada por el script inyectado).
|
||||
// Es global y no por pestaña: todas las pestañas del origen comparten el mismo modo
|
||||
// (localStorage). None = ninguna pestaña ha reportado aún → la barra cae al del sistema.
|
||||
dark: Option<bool>,
|
||||
}
|
||||
|
||||
impl Default for Shell {
|
||||
fn default() -> Self {
|
||||
Self { tabs: Vec::new(), active: 0, zoom_idx: ZOOM_100, next_id: 1, win: None, autostart: None, closed: Vec::new(), menu_tab: None, maximized: false }
|
||||
Self { tabs: Vec::new(), active: 0, zoom_idx: ZOOM_100, next_id: 1, win: None, autostart: None, closed: Vec::new(), menu_tab: None, maximized: false, dark: None }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +136,10 @@ struct Persisted {
|
||||
active: usize,
|
||||
#[serde(default)]
|
||||
autostart: Option<bool>,
|
||||
// Último tema conocido de la app: la barra abre ya del color correcto en vez de
|
||||
// arrancar con el del sistema y dar un fogonazo al cargar la primera pestaña.
|
||||
#[serde(default)]
|
||||
dark: Option<bool>,
|
||||
}
|
||||
|
||||
fn state_path<R: Runtime>(app: &AppHandle<R>) -> Option<std::path::PathBuf> {
|
||||
@@ -157,6 +165,7 @@ fn save_persisted<R: Runtime>(app: &AppHandle<R>) {
|
||||
colors: s.tabs.iter().map(|t| t.color.clone().unwrap_or_default()).collect(),
|
||||
active: s.active,
|
||||
autostart: s.autostart,
|
||||
dark: s.dark,
|
||||
}
|
||||
};
|
||||
if let Some(dir) = path.parent() {
|
||||
@@ -212,9 +221,16 @@ const INIT_JS_TPL: &str = r#"
|
||||
}
|
||||
function newTab(url) { emit('ls-new-tab', { url: url }); }
|
||||
|
||||
// Título y URL → barra de pestañas (y para restaurar la sesión al reabrir).
|
||||
// Título, URL y TEMA → barra de pestañas (y para restaurar la sesión al reabrir).
|
||||
// El tema viaja aquí porque la web decide su modo con su propio botón (next-themes,
|
||||
// clase `dark` en <html>) y NO sigue al sistema: la barra tiene que copiarle a ella.
|
||||
function report() {
|
||||
emit('ls-title', { label: LABEL, title: document.title || 'Labstream OS', url: location.href });
|
||||
emit('ls-title', {
|
||||
label: LABEL,
|
||||
title: document.title || 'Labstream OS',
|
||||
url: location.href,
|
||||
dark: document.documentElement.classList.contains('dark')
|
||||
});
|
||||
}
|
||||
function watchTitle() {
|
||||
var el = document.querySelector('title');
|
||||
@@ -233,6 +249,11 @@ const INIT_JS_TPL: &str = r#"
|
||||
history[k] = function () { var r = o.apply(this, arguments); setTimeout(report, 0); return r; };
|
||||
});
|
||||
addEventListener('popstate', function () { setTimeout(report, 0); });
|
||||
// Pulsar la luna cambia la clase de <html> sin navegar: se observa para que la barra
|
||||
// se entere al instante.
|
||||
if (window.MutationObserver) {
|
||||
new MutationObserver(report).observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
|
||||
}
|
||||
|
||||
// Clics en enlaces.
|
||||
document.addEventListener('click', function (e) {
|
||||
@@ -368,6 +389,8 @@ fn broadcast<R: Runtime>(app: &AppHandle<R>) {
|
||||
// que dibuja ella misma los botones minimizar/maximizar/cerrar (no hay nativos).
|
||||
"win": cfg!(target_os = "windows"),
|
||||
"maximized": maximized,
|
||||
// Tema real de la app (null hasta el primer reporte: la barra usa el del sistema).
|
||||
"dark": s.dark,
|
||||
})
|
||||
};
|
||||
let _ = app.emit_to(EventTarget::webview(CHROME), "ls-tabs", payload);
|
||||
@@ -945,6 +968,10 @@ fn field_i64(payload: &str, key: &str) -> Option<i64> {
|
||||
serde_json::from_str::<serde_json::Value>(payload).ok()?.get(key)?.as_i64()
|
||||
}
|
||||
|
||||
fn field_bool(payload: &str, key: &str) -> Option<bool> {
|
||||
serde_json::from_str::<serde_json::Value>(payload).ok()?.get(key)?.as_bool()
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
let mut builder = tauri::Builder::default();
|
||||
@@ -994,6 +1021,7 @@ pub fn run() {
|
||||
s.zoom_idx = saved.zoom_idx.unwrap_or(ZOOM_100).min(ZOOM_STEPS.len() - 1);
|
||||
s.win = saved.win;
|
||||
s.autostart = saved.autostart;
|
||||
s.dark = saved.dark;
|
||||
}
|
||||
|
||||
// Ventana principal (contenedora). Tamaño/posición de la sesión anterior si los hay.
|
||||
@@ -1308,10 +1336,11 @@ pub fn run() {
|
||||
});
|
||||
let h = handle.clone();
|
||||
handle.listen_any("ls-title", move |e| {
|
||||
let (label, title, url) = (
|
||||
let (label, title, url, dark) = (
|
||||
field(e.payload(), "label"),
|
||||
field(e.payload(), "title"),
|
||||
field(e.payload(), "url"),
|
||||
field_bool(e.payload(), "dark"),
|
||||
);
|
||||
if let Some(label) = label {
|
||||
let shell = h.state::<ShellState>();
|
||||
@@ -1324,6 +1353,11 @@ pub fn run() {
|
||||
t.url = url;
|
||||
}
|
||||
}
|
||||
// El tema es global (todas las pestañas del origen comparten modo): el
|
||||
// último reporte manda, venga de la pestaña que venga.
|
||||
if let Some(dark) = dark {
|
||||
s.dark = Some(dark);
|
||||
}
|
||||
drop(s);
|
||||
broadcast(&h);
|
||||
save_persisted(&h);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Labstream OS",
|
||||
"version": "1.6.0",
|
||||
"version": "1.7.0",
|
||||
"identifier": "co.labstream.os",
|
||||
"build": {
|
||||
"frontendDist": "../dist"
|
||||
|
||||
Referencia en una nueva incidencia
Block a user