Comparar commits
3 Commits
barra-en-e
...
barra-tema
| Autor | SHA1 | Fecha | |
|---|---|---|---|
|
|
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).
|
Barra de pestañas del shell de escritorio (webview "chrome", local).
|
||||||
Habla con Rust por eventos (__TAURI__.event):
|
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,
|
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
|
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
|
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.
|
(`data-tauri-drag-region`). En Windows hay título nativo aparte y no se reserva nada.
|
||||||
-->
|
-->
|
||||||
<style>
|
<style>
|
||||||
/* Paleta estilo Chrome: la barra es una FRANJA más oscura y la pestaña activa es una
|
/* La barra se pinta del color de la APP, no del gris de Chrome: blanca en tema claro y
|
||||||
tarjeta del color del contenido que se funde con él (sin línea debajo). */
|
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 {
|
:root {
|
||||||
--bg: #dee1e6; /* franja de pestañas (Chrome claro) */
|
--bg: #f4f5f7; /* franja: casi blanca, del tono de la app en claro */
|
||||||
--fg: #45474a;
|
--fg: #45474a;
|
||||||
--muted: #5f6368;
|
--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;
|
--tab-active-fg: #202124;
|
||||||
--accent: #6366f1; /* indigo-500, color de Labstream */
|
--accent: #F47A20; /* NARANJA Labstream (el índigo de antes no era de la marca) */
|
||||||
--border: #c9ccd1; /* línea bajo la barra (la tapa la pestaña activa) */
|
--border: #dcdee1; /* 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 */
|
/* 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);
|
--btn-hover: rgba(0, 0, 0, 0.08);
|
||||||
--sep: rgba(0, 0, 0, 0.22); /* separador fino entre pestañas inactivas */
|
--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) {
|
@media (prefers-color-scheme: dark) {
|
||||||
:root {
|
:root:not(.claro) {
|
||||||
--bg: #202124; /* franja (Chrome oscuro) */
|
--bg: #0d0d0f; /* franja: negra, más oscura que el contenido */
|
||||||
--fg: #bdc1c6;
|
--fg: #bdc1c6;
|
||||||
--muted: #80868b;
|
--muted: #80868b;
|
||||||
--tab-active-bg: #18181b; /* mismo fondo que la app → pestaña fundida con el contenido */
|
--tab-active-bg: #18181b; /* mismo fondo que la app → pestaña fundida con el contenido */
|
||||||
--tab-active-fg: #e8eaed;
|
--tab-active-fg: #e8eaed;
|
||||||
--border: #101013;
|
--border: #26262a;
|
||||||
--hover: rgba(255, 255, 255, 0.08);
|
--hover: rgba(255, 255, 255, 0.08);
|
||||||
--btn-hover: rgba(255, 255, 255, 0.1);
|
--btn-hover: rgba(255, 255, 255, 0.1);
|
||||||
--sep: rgba(255, 255, 255, 0.25);
|
--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; }
|
* { box-sizing: border-box; }
|
||||||
html, body {
|
html, body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -81,10 +109,13 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 7px;
|
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;
|
flex: 1 1 220px;
|
||||||
min-width: 42px;
|
min-width: 42px;
|
||||||
max-width: 240px;
|
max-width: 280px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
padding: 0 6px 0 10px;
|
padding: 0 6px 0 10px;
|
||||||
@@ -158,9 +189,11 @@
|
|||||||
.tab[data-color]:not(.active)::before { display: none; }
|
.tab[data-color]:not(.active)::before { display: none; }
|
||||||
.tab[data-color].active { box-shadow: inset 0 3px 0 rgb(var(--tc)); }
|
.tab[data-color].active { box-shadow: inset 0 3px 0 rgb(var(--tc)); }
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
.tab[data-color]:not(.active) { background: rgba(var(--tc), 0.36); }
|
:root:not(.claro) .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):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
|
/* 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
|
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. */
|
WebKit para que no salga la imagen fantasma al mover una pestaña. */
|
||||||
@@ -498,6 +531,12 @@
|
|||||||
$pct.textContent = estado.zoom + "%";
|
$pct.textContent = estado.zoom + "%";
|
||||||
$pct.classList.toggle("activo", estado.zoom !== 100);
|
$pct.classList.toggle("activo", estado.zoom !== 100);
|
||||||
$ver.textContent = estado.version ? "v" + estado.version : "";
|
$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.
|
// En macOS la barra ES la fila del título: hay que dejar libre el semáforo.
|
||||||
document.body.classList.toggle("mac", !!estado.mac);
|
document.body.classList.toggle("mac", !!estado.mac);
|
||||||
// En Windows también ES la fila del título, pero ahí los botones de ventana los
|
// En Windows también ES la fila del título, pero ahí los botones de ventana los
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "labstream-desktop",
|
"name": "labstream-desktop",
|
||||||
"version": "1.6.0",
|
"version": "1.6.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "App de escritorio (Windows) que envuelve Labstream OS — cliente del servidor del NAS.",
|
"description": "App de escritorio (Windows) que envuelve Labstream OS — cliente del servidor del NAS.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
2
src-tauri/Cargo.lock
generado
2
src-tauri/Cargo.lock
generado
@@ -2062,7 +2062,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "labstream-desktop"
|
name = "labstream-desktop"
|
||||||
version = "1.6.0"
|
version = "1.6.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "labstream-desktop"
|
name = "labstream-desktop"
|
||||||
version = "1.6.0"
|
version = "1.6.1"
|
||||||
description = "Cliente de escritorio de Labstream OS"
|
description = "Cliente de escritorio de Labstream OS"
|
||||||
authors = ["Labstream SAS"]
|
authors = ["Labstream SAS"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -97,11 +97,15 @@ struct Shell {
|
|||||||
// Último estado maximizado conocido. Solo sirve para no repintar la barra en CADA
|
// Último estado maximizado conocido. Solo sirve para no repintar la barra en CADA
|
||||||
// evento de redimensión: únicamente cuando el icono ▢/❐ tiene que cambiar.
|
// evento de redimensión: únicamente cuando el icono ▢/❐ tiene que cambiar.
|
||||||
maximized: bool,
|
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 {
|
impl Default for Shell {
|
||||||
fn default() -> Self {
|
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,
|
active: usize,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
autostart: Option<bool>,
|
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> {
|
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(),
|
colors: s.tabs.iter().map(|t| t.color.clone().unwrap_or_default()).collect(),
|
||||||
active: s.active,
|
active: s.active,
|
||||||
autostart: s.autostart,
|
autostart: s.autostart,
|
||||||
|
dark: s.dark,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(dir) = path.parent() {
|
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 }); }
|
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() {
|
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() {
|
function watchTitle() {
|
||||||
var el = document.querySelector('title');
|
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; };
|
history[k] = function () { var r = o.apply(this, arguments); setTimeout(report, 0); return r; };
|
||||||
});
|
});
|
||||||
addEventListener('popstate', function () { setTimeout(report, 0); });
|
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.
|
// Clics en enlaces.
|
||||||
document.addEventListener('click', function (e) {
|
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).
|
// que dibuja ella misma los botones minimizar/maximizar/cerrar (no hay nativos).
|
||||||
"win": cfg!(target_os = "windows"),
|
"win": cfg!(target_os = "windows"),
|
||||||
"maximized": maximized,
|
"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);
|
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()
|
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)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
let mut builder = tauri::Builder::default();
|
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.zoom_idx = saved.zoom_idx.unwrap_or(ZOOM_100).min(ZOOM_STEPS.len() - 1);
|
||||||
s.win = saved.win;
|
s.win = saved.win;
|
||||||
s.autostart = saved.autostart;
|
s.autostart = saved.autostart;
|
||||||
|
s.dark = saved.dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ventana principal (contenedora). Tamaño/posición de la sesión anterior si los hay.
|
// 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();
|
let h = handle.clone();
|
||||||
handle.listen_any("ls-title", move |e| {
|
handle.listen_any("ls-title", move |e| {
|
||||||
let (label, title, url) = (
|
let (label, title, url, dark) = (
|
||||||
field(e.payload(), "label"),
|
field(e.payload(), "label"),
|
||||||
field(e.payload(), "title"),
|
field(e.payload(), "title"),
|
||||||
field(e.payload(), "url"),
|
field(e.payload(), "url"),
|
||||||
|
field_bool(e.payload(), "dark"),
|
||||||
);
|
);
|
||||||
if let Some(label) = label {
|
if let Some(label) = label {
|
||||||
let shell = h.state::<ShellState>();
|
let shell = h.state::<ShellState>();
|
||||||
@@ -1324,6 +1353,11 @@ pub fn run() {
|
|||||||
t.url = url;
|
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);
|
drop(s);
|
||||||
broadcast(&h);
|
broadcast(&h);
|
||||||
save_persisted(&h);
|
save_persisted(&h);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Labstream OS",
|
"productName": "Labstream OS",
|
||||||
"version": "1.6.0",
|
"version": "1.6.1",
|
||||||
"identifier": "co.labstream.os",
|
"identifier": "co.labstream.os",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../dist"
|
"frontendDist": "../dist"
|
||||||
|
|||||||
Referencia en una nueva incidencia
Block a user