feat(tahuantinsuyu): fase 22 — layout splitter + atlas loadable desde XDG
Dos features de producción que mejoran la usabilidad sustancialmente. ## #7 — Layout reorganizable con SplitContainer Los 3 paneles ya no tienen tamaños hardcodeados. Reusamos yahweh-widget-splitter (mismo que usa yahweh-shell para sus layouts JSON-config) con 2 niveles: - outer (Vertical): main_split arriba (flex 4) + panel abajo (flex 1) - main_split (Horizontal): tree (flex 1) + canvas (flex 4) El usuario puede arrastrar los dos divisores para redimensionar libremente. Por ejemplo: en una pantalla ancha, dar más al canvas; en una sesión de lectura analítica, agrandar el panel abajo para ver más módulos expandidos. - Shell gana fields main_split + outer_split: Entity<SplitContainer>. - new() construye ambos con ChildSlots envolviendo tree/canvas/panel como AnyView (mismo patrón que LayoutHost de yahweh-shell). - render() simplificado: header + body(outer_split). Las constants TREE_WIDTH y PANEL_HEIGHT desaparecen. - Cargo añade deps: yahweh-core (NodeId, LayoutDirection), yahweh-widget-splitter, yahweh-widget-container-core (ChildSlot). ## #15 — Atlas de ciudades cargable desde TSV El array `CITY_PRESETS` const de 90 ciudades hardcoded ahora es la función `default_city_presets() -> Vec<CityPreset>`. CityPreset.name pasa de `&'static str` a `String` para que el atlas sea construible en runtime. TahuantinsuyuTree gana `city_atlas: Vec<CityPreset>` + setter `set_city_atlas(atlas, cx)`. Al boot, Shell intenta cargar `$XDG_DATA_HOME/tahuantinsuyu/atlas.tsv` y, si existe + parsea bien, reemplaza el atlas hardcoded. Formato TSV (líneas): name<TAB>lat<TAB>lon<TAB>tz_offset_minutes Líneas vacías y `#` comentario se ignoran. Líneas con cualquier parse fallido se descartan en silencio. API pública: `parse_city_atlas_tsv(&str) -> Vec<CityPreset>` (en tahuantinsuyu-tree), reusable por tests/scripts. El usuario que quiera 50.000 ciudades de GeoNames cities5000.txt: 1. wget cities5000.zip de geonames.org 2. awk para extraer (name, lat, lon, tz_offset) y escribir TSV 3. mover a $XDG_DATA_HOME/tahuantinsuyu/atlas.tsv 4. relanzar la app Sin fricción adicional para el usuario común (los 90 hardcoded cubren 99% de casos típicos en español/inglés). cargo check verde, 8 tests engine + 1 test modules verdes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Generated
+3
@@ -10917,7 +10917,10 @@ dependencies = [
|
||||
"tahuantinsuyu-theme",
|
||||
"tahuantinsuyu-tree",
|
||||
"yahweh-bus",
|
||||
"yahweh-core",
|
||||
"yahweh-theme",
|
||||
"yahweh-widget-container-core",
|
||||
"yahweh-widget-splitter",
|
||||
"yahweh-widget-theme-switcher",
|
||||
]
|
||||
|
||||
|
||||
@@ -17,8 +17,11 @@ tahuantinsuyu-theme = { path = "../../modules/tahuantinsuyu/tahuantinsuyu-theme"
|
||||
tahuantinsuyu-tree = { path = "../../modules/tahuantinsuyu/tahuantinsuyu-tree" }
|
||||
|
||||
yahweh-bus = { workspace = true }
|
||||
yahweh-core = { workspace = true }
|
||||
yahweh-theme = { workspace = true }
|
||||
yahweh-widget-theme-switcher = { path = "../../modules/ui_engine/widgets/theme-switcher" }
|
||||
yahweh-widget-splitter = { workspace = true }
|
||||
yahweh-widget-container-core = { workspace = true }
|
||||
gpui = { workspace = true }
|
||||
directories = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -38,9 +38,12 @@ use tahuantinsuyu_engine::{
|
||||
use tahuantinsuyu_model::{Chart, ChartId, ModuleState, TreeSelection};
|
||||
use tahuantinsuyu_panel::{ChartOption, ControlPanel, PanelEvent};
|
||||
use tahuantinsuyu_store::Store;
|
||||
use tahuantinsuyu_tree::{TahuantinsuyuTree, TreeEvent};
|
||||
use tahuantinsuyu_tree::{parse_city_atlas_tsv, TahuantinsuyuTree, TreeEvent};
|
||||
use yahweh_bus::AppBus;
|
||||
use yahweh_core::{LayoutDirection, NodeId};
|
||||
use yahweh_theme::Theme;
|
||||
use yahweh_widget_container_core::ChildSlot;
|
||||
use yahweh_widget_splitter::SplitContainer;
|
||||
use yahweh_widget_theme_switcher::theme_switcher;
|
||||
|
||||
const TREE_WIDTH: f32 = 280.0;
|
||||
@@ -53,6 +56,13 @@ pub struct Shell {
|
||||
tree: Entity<TahuantinsuyuTree>,
|
||||
canvas: Entity<AstrologyCanvas>,
|
||||
panel: Entity<ControlPanel>,
|
||||
/// Splitter horizontal entre tree (izq) y canvas (der). El divisor
|
||||
/// es draggable — el flex se persiste in-memory mientras la app
|
||||
/// está abierta.
|
||||
main_split: Entity<SplitContainer>,
|
||||
/// Splitter vertical entre el main_row (arriba) y el panel de
|
||||
/// control (abajo).
|
||||
outer_split: Entity<SplitContainer>,
|
||||
current_chart: Option<Chart>,
|
||||
current_offset_minutes: i64,
|
||||
/// Estado de los módulos overlay (transit, progression, …) por
|
||||
@@ -71,7 +81,16 @@ impl Shell {
|
||||
cx.observe_global::<Theme>(|_, cx| cx.notify()).detach();
|
||||
|
||||
let bus = cx.new(|_| AppBus);
|
||||
let tree = cx.new(|cx| TahuantinsuyuTree::new(store.clone(), cx));
|
||||
let tree = cx.new(|cx| {
|
||||
let mut t = TahuantinsuyuTree::new(store.clone(), cx);
|
||||
// Si hay un atlas custom en $XDG_DATA_HOME/tahuantinsuyu/
|
||||
// atlas.tsv, lo cargamos y reemplazamos el atlas hardcoded
|
||||
// de 90 ciudades. Formato TSV: name<TAB>lat<TAB>lon<TAB>tz_min.
|
||||
if let Some(atlas) = load_city_atlas_from_xdg() {
|
||||
t.set_city_atlas(atlas, cx);
|
||||
}
|
||||
t
|
||||
});
|
||||
let canvas = cx.new(AstrologyCanvas::new);
|
||||
let panel = cx.new(ControlPanel::new);
|
||||
|
||||
@@ -90,12 +109,59 @@ impl Shell {
|
||||
})
|
||||
.detach();
|
||||
|
||||
// Splitter horizontal: tree + canvas (flex 1 : 4).
|
||||
let main_split = cx.new(|cx| SplitContainer::new(LayoutDirection::Horizontal, cx));
|
||||
main_split.update(cx, |sc, cx| {
|
||||
sc.set_children(
|
||||
vec![
|
||||
ChildSlot {
|
||||
id: NodeId::new("tts-tree"),
|
||||
flex: 1.0,
|
||||
label: None,
|
||||
view: gpui::AnyView::from(tree.clone()),
|
||||
},
|
||||
ChildSlot {
|
||||
id: NodeId::new("tts-canvas"),
|
||||
flex: 4.0,
|
||||
label: None,
|
||||
view: gpui::AnyView::from(canvas.clone()),
|
||||
},
|
||||
],
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
// Splitter vertical: main_split arriba, panel abajo (flex 4 : 1).
|
||||
let outer_split = cx.new(|cx| {
|
||||
let mut sc = SplitContainer::new(LayoutDirection::Vertical, cx);
|
||||
sc.set_children(
|
||||
vec![
|
||||
ChildSlot {
|
||||
id: NodeId::new("tts-main"),
|
||||
flex: 4.0,
|
||||
label: None,
|
||||
view: gpui::AnyView::from(main_split.clone()),
|
||||
},
|
||||
ChildSlot {
|
||||
id: NodeId::new("tts-panel"),
|
||||
flex: 1.0,
|
||||
label: None,
|
||||
view: gpui::AnyView::from(panel.clone()),
|
||||
},
|
||||
],
|
||||
cx,
|
||||
);
|
||||
sc
|
||||
});
|
||||
|
||||
let mut shell = Self {
|
||||
store,
|
||||
bus,
|
||||
tree,
|
||||
canvas,
|
||||
panel,
|
||||
main_split,
|
||||
outer_split,
|
||||
current_chart: None,
|
||||
current_offset_minutes: 0,
|
||||
module_configs: HashMap::new(),
|
||||
@@ -706,6 +772,28 @@ impl Shell {
|
||||
// truth. Shell y canvas leen del mismo slice.
|
||||
|
||||
|
||||
/// Lee `$XDG_DATA_HOME/tahuantinsuyu/atlas.tsv` si existe y lo parsea
|
||||
/// como atlas de ciudades. Devuelve `None` cuando no hay archivo o
|
||||
/// quedó vacío después del parse — el tree cae al atlas hardcoded.
|
||||
fn load_city_atlas_from_xdg() -> Option<Vec<tahuantinsuyu_tree::CityPreset>> {
|
||||
let path = directories::ProjectDirs::from("net", "gioser", "tahuantinsuyu")
|
||||
.map(|d| d.data_dir().join("atlas.tsv"))?;
|
||||
if !path.exists() {
|
||||
return None;
|
||||
}
|
||||
let content = std::fs::read_to_string(&path).ok()?;
|
||||
let atlas = parse_city_atlas_tsv(&content);
|
||||
if atlas.is_empty() {
|
||||
eprintln!(
|
||||
"[shell] atlas.tsv encontrado en {:?} pero sin filas válidas — fallback a hardcoded",
|
||||
path
|
||||
);
|
||||
return None;
|
||||
}
|
||||
eprintln!("[shell] atlas custom cargado: {} ciudades", atlas.len());
|
||||
Some(atlas)
|
||||
}
|
||||
|
||||
/// Etiqueta breve para mostrar al elegir una carta en el picker:
|
||||
/// `"YYYY-MM-DD · Lugar"` cuando hay lugar, sino solo la fecha.
|
||||
fn format_birth_brief(birth: &tahuantinsuyu_model::StoredBirthData) -> String {
|
||||
@@ -780,30 +868,10 @@ impl Render for Shell {
|
||||
.child(div().flex_grow())
|
||||
.child(theme_switcher(cx));
|
||||
|
||||
let tree_panel = div()
|
||||
.w(px(TREE_WIDTH))
|
||||
.min_w(px(TREE_WIDTH))
|
||||
.h_full()
|
||||
.border_r_1()
|
||||
.border_color(theme.border)
|
||||
.child(self.tree.clone());
|
||||
|
||||
let canvas_panel = div().flex_grow().h_full().child(self.canvas.clone());
|
||||
|
||||
let main_row = div()
|
||||
let body = div()
|
||||
.flex_grow()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.child(tree_panel)
|
||||
.child(canvas_panel);
|
||||
|
||||
let bottom_panel = div()
|
||||
.h(px(PANEL_HEIGHT))
|
||||
.min_h(px(PANEL_HEIGHT))
|
||||
.w_full()
|
||||
.border_t_1()
|
||||
.border_color(theme.border)
|
||||
.child(self.panel.clone());
|
||||
.child(self.outer_split.clone());
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
@@ -811,7 +879,6 @@ impl Render for Shell {
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(header)
|
||||
.child(main_row)
|
||||
.child(bottom_panel)
|
||||
.child(body)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,127 +151,173 @@ pub struct TahuantinsuyuTree {
|
||||
/// está abierto. Vive en el tree (no en ChartForm) porque las
|
||||
/// closures de los click handlers necesitan mutarlo via `cx.listener`.
|
||||
city_picker_open: bool,
|
||||
/// Atlas de ciudades para el dropdown del form. Se inicializa con
|
||||
/// `default_city_presets()` (90 ciudades hardcoded). El host puede
|
||||
/// llamar [`Self::set_city_atlas`] para reemplazar por uno custom
|
||||
/// cargado desde disco (TSV).
|
||||
city_atlas: Vec<CityPreset>,
|
||||
}
|
||||
|
||||
/// Preset de ciudad con datos canónicos para autocompletar lat/lon/tz
|
||||
/// al elegirlo en el form. TZ es la zona estándar **sin DST** — el
|
||||
/// usuario afina si necesita.
|
||||
#[derive(Clone, Copy)]
|
||||
struct CityPreset {
|
||||
name: &'static str,
|
||||
lat: f64,
|
||||
lon: f64,
|
||||
tz_offset_minutes: i32,
|
||||
/// usuario afina si necesita. `name` es `String` (no &'static) para
|
||||
/// permitir cargar atlas custom desde disco vía
|
||||
/// [`TahuantinsuyuTree::set_city_atlas`].
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CityPreset {
|
||||
pub name: String,
|
||||
pub lat: f64,
|
||||
pub lon: f64,
|
||||
pub tz_offset_minutes: i32,
|
||||
}
|
||||
|
||||
const CITY_PRESETS: &[CityPreset] = &[
|
||||
/// Atlas hardcoded — 90 ciudades canónicas que cubren la mayoría de
|
||||
/// casos de uso. El usuario puede sobrescribirlas pasando un atlas
|
||||
/// custom vía [`TahuantinsuyuTree::set_city_atlas`] (típicamente
|
||||
/// cargado desde `$XDG_DATA_HOME/tahuantinsuyu/atlas.tsv`).
|
||||
pub fn default_city_presets() -> Vec<CityPreset> {
|
||||
vec![
|
||||
// Latinoamérica
|
||||
CityPreset { name: "Buenos Aires, AR", lat: -34.6037, lon: -58.3816, tz_offset_minutes: -180 },
|
||||
CityPreset { name: "Córdoba, AR", lat: -31.4201, lon: -64.1888, tz_offset_minutes: -180 },
|
||||
CityPreset { name: "Rosario, AR", lat: -32.9587, lon: -60.6930, tz_offset_minutes: -180 },
|
||||
CityPreset { name: "Mendoza, AR", lat: -32.8908, lon: -68.8272, tz_offset_minutes: -180 },
|
||||
CityPreset { name: "Caracas, VE", lat: 10.4806, lon: -66.9036, tz_offset_minutes: -240 },
|
||||
CityPreset { name: "Maracaibo, VE", lat: 10.6427, lon: -71.6125, tz_offset_minutes: -240 },
|
||||
CityPreset { name: "Valencia, VE", lat: 10.1620, lon: -68.0078, tz_offset_minutes: -240 },
|
||||
CityPreset { name: "Bogotá, CO", lat: 4.7110, lon: -74.0721, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Medellín, CO", lat: 6.2442, lon: -75.5812, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Cali, CO", lat: 3.4516, lon: -76.5320, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Lima, PE", lat: -12.0464, lon: -77.0428, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Cusco, PE", lat: -13.5319, lon: -71.9675, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Santiago, CL", lat: -33.4489, lon: -70.6693, tz_offset_minutes: -240 },
|
||||
CityPreset { name: "Valparaíso, CL", lat: -33.0472, lon: -71.6127, tz_offset_minutes: -240 },
|
||||
CityPreset { name: "Quito, EC", lat: -0.1807, lon: -78.4678, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Guayaquil, EC", lat: -2.1709, lon: -79.9224, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Montevideo, UY", lat: -34.9011, lon: -56.1645, tz_offset_minutes: -180 },
|
||||
CityPreset { name: "Asunción, PY", lat: -25.2637, lon: -57.5759, tz_offset_minutes: -240 },
|
||||
CityPreset { name: "La Paz, BO", lat: -16.4897, lon: -68.1193, tz_offset_minutes: -240 },
|
||||
CityPreset { name: "Ciudad de México", lat: 19.4326, lon: -99.1332, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "Guadalajara, MX", lat: 20.6597, lon: -103.3496, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "Monterrey, MX", lat: 25.6866, lon: -100.3161, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "Habana, CU", lat: 23.1136, lon: -82.3666, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "San Juan, PR", lat: 18.4655, lon: -66.1057, tz_offset_minutes: -240 },
|
||||
CityPreset { name: "San José, CR", lat: 9.9281, lon: -84.0907, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "Panamá, PA", lat: 8.9824, lon: -79.5199, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "San Salvador, SV", lat: 13.6929, lon: -89.2182, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "Guatemala, GT", lat: 14.6349, lon: -90.5069, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "Tegucigalpa, HN", lat: 14.0723, lon: -87.1921, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "Managua, NI", lat: 12.1149, lon: -86.2362, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "Santo Domingo, DO", lat: 18.4861, lon: -69.9312, tz_offset_minutes: -240 },
|
||||
CityPreset { name: "São Paulo, BR", lat: -23.5505, lon: -46.6333, tz_offset_minutes: -180 },
|
||||
CityPreset { name: "Rio de Janeiro, BR", lat: -22.9068, lon: -43.1729, tz_offset_minutes: -180 },
|
||||
CityPreset { name: "Brasília, BR", lat: -15.8267, lon: -47.9218, tz_offset_minutes: -180 },
|
||||
CityPreset { name: "Salvador, BR", lat: -12.9777, lon: -38.5016, tz_offset_minutes: -180 },
|
||||
CityPreset { name: "Buenos Aires, AR".into(), lat: -34.6037, lon: -58.3816, tz_offset_minutes: -180 },
|
||||
CityPreset { name: "Córdoba, AR".into(), lat: -31.4201, lon: -64.1888, tz_offset_minutes: -180 },
|
||||
CityPreset { name: "Rosario, AR".into(), lat: -32.9587, lon: -60.6930, tz_offset_minutes: -180 },
|
||||
CityPreset { name: "Mendoza, AR".into(), lat: -32.8908, lon: -68.8272, tz_offset_minutes: -180 },
|
||||
CityPreset { name: "Caracas, VE".into(), lat: 10.4806, lon: -66.9036, tz_offset_minutes: -240 },
|
||||
CityPreset { name: "Maracaibo, VE".into(), lat: 10.6427, lon: -71.6125, tz_offset_minutes: -240 },
|
||||
CityPreset { name: "Valencia, VE".into(), lat: 10.1620, lon: -68.0078, tz_offset_minutes: -240 },
|
||||
CityPreset { name: "Bogotá, CO".into(), lat: 4.7110, lon: -74.0721, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Medellín, CO".into(), lat: 6.2442, lon: -75.5812, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Cali, CO".into(), lat: 3.4516, lon: -76.5320, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Lima, PE".into(), lat: -12.0464, lon: -77.0428, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Cusco, PE".into(), lat: -13.5319, lon: -71.9675, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Santiago, CL".into(), lat: -33.4489, lon: -70.6693, tz_offset_minutes: -240 },
|
||||
CityPreset { name: "Valparaíso, CL".into(), lat: -33.0472, lon: -71.6127, tz_offset_minutes: -240 },
|
||||
CityPreset { name: "Quito, EC".into(), lat: -0.1807, lon: -78.4678, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Guayaquil, EC".into(), lat: -2.1709, lon: -79.9224, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Montevideo, UY".into(), lat: -34.9011, lon: -56.1645, tz_offset_minutes: -180 },
|
||||
CityPreset { name: "Asunción, PY".into(), lat: -25.2637, lon: -57.5759, tz_offset_minutes: -240 },
|
||||
CityPreset { name: "La Paz, BO".into(), lat: -16.4897, lon: -68.1193, tz_offset_minutes: -240 },
|
||||
CityPreset { name: "Ciudad de México".into(), lat: 19.4326, lon: -99.1332, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "Guadalajara, MX".into(), lat: 20.6597, lon: -103.3496, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "Monterrey, MX".into(), lat: 25.6866, lon: -100.3161, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "Habana, CU".into(), lat: 23.1136, lon: -82.3666, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "San Juan, PR".into(), lat: 18.4655, lon: -66.1057, tz_offset_minutes: -240 },
|
||||
CityPreset { name: "San José, CR".into(), lat: 9.9281, lon: -84.0907, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "Panamá, PA".into(), lat: 8.9824, lon: -79.5199, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "San Salvador, SV".into(), lat: 13.6929, lon: -89.2182, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "Guatemala, GT".into(), lat: 14.6349, lon: -90.5069, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "Tegucigalpa, HN".into(), lat: 14.0723, lon: -87.1921, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "Managua, NI".into(), lat: 12.1149, lon: -86.2362, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "Santo Domingo, DO".into(), lat: 18.4861, lon: -69.9312, tz_offset_minutes: -240 },
|
||||
CityPreset { name: "São Paulo, BR".into(), lat: -23.5505, lon: -46.6333, tz_offset_minutes: -180 },
|
||||
CityPreset { name: "Rio de Janeiro, BR".into(), lat: -22.9068, lon: -43.1729, tz_offset_minutes: -180 },
|
||||
CityPreset { name: "Brasília, BR".into(), lat: -15.8267, lon: -47.9218, tz_offset_minutes: -180 },
|
||||
CityPreset { name: "Salvador, BR".into(), lat: -12.9777, lon: -38.5016, tz_offset_minutes: -180 },
|
||||
// España
|
||||
CityPreset { name: "Madrid, ES", lat: 40.4168, lon: -3.7038, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Barcelona, ES", lat: 41.3851, lon: 2.1734, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Sevilla, ES", lat: 37.3891, lon: -5.9845, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Valencia, ES", lat: 39.4699, lon: -0.3763, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Bilbao, ES", lat: 43.2630, lon: -2.9350, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Madrid, ES".into(), lat: 40.4168, lon: -3.7038, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Barcelona, ES".into(), lat: 41.3851, lon: 2.1734, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Sevilla, ES".into(), lat: 37.3891, lon: -5.9845, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Valencia, ES".into(), lat: 39.4699, lon: -0.3763, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Bilbao, ES".into(), lat: 43.2630, lon: -2.9350, tz_offset_minutes: 60 },
|
||||
// Europa
|
||||
CityPreset { name: "London, UK", lat: 51.5074, lon: -0.1278, tz_offset_minutes: 0 },
|
||||
CityPreset { name: "Paris, FR", lat: 48.8566, lon: 2.3522, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Berlin, DE", lat: 52.5200, lon: 13.4050, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "München, DE", lat: 48.1351, lon: 11.5820, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Roma, IT", lat: 41.9028, lon: 12.4964, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Milano, IT", lat: 45.4642, lon: 9.1900, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Amsterdam, NL", lat: 52.3676, lon: 4.9041, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Bruxelles, BE", lat: 50.8503, lon: 4.3517, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Wien, AT", lat: 48.2082, lon: 16.3738, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Zürich, CH", lat: 47.3769, lon: 8.5417, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Lisboa, PT", lat: 38.7223, lon: -9.1393, tz_offset_minutes: 0 },
|
||||
CityPreset { name: "Dublin, IE", lat: 53.3498, lon: -6.2603, tz_offset_minutes: 0 },
|
||||
CityPreset { name: "Stockholm, SE", lat: 59.3293, lon: 18.0686, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Oslo, NO", lat: 59.9139, lon: 10.7522, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "København, DK", lat: 55.6761, lon: 12.5683, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Helsinki, FI", lat: 60.1699, lon: 24.9384, tz_offset_minutes: 120 },
|
||||
CityPreset { name: "Warszawa, PL", lat: 52.2297, lon: 21.0122, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Praha, CZ", lat: 50.0755, lon: 14.4378, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Budapest, HU", lat: 47.4979, lon: 19.0402, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Athina, GR", lat: 37.9838, lon: 23.7275, tz_offset_minutes: 120 },
|
||||
CityPreset { name: "İstanbul, TR", lat: 41.0082, lon: 28.9784, tz_offset_minutes: 180 },
|
||||
CityPreset { name: "Moskva, RU", lat: 55.7558, lon: 37.6173, tz_offset_minutes: 180 },
|
||||
CityPreset { name: "London, UK".into(), lat: 51.5074, lon: -0.1278, tz_offset_minutes: 0 },
|
||||
CityPreset { name: "Paris, FR".into(), lat: 48.8566, lon: 2.3522, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Berlin, DE".into(), lat: 52.5200, lon: 13.4050, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "München, DE".into(), lat: 48.1351, lon: 11.5820, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Roma, IT".into(), lat: 41.9028, lon: 12.4964, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Milano, IT".into(), lat: 45.4642, lon: 9.1900, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Amsterdam, NL".into(), lat: 52.3676, lon: 4.9041, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Bruxelles, BE".into(), lat: 50.8503, lon: 4.3517, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Wien, AT".into(), lat: 48.2082, lon: 16.3738, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Zürich, CH".into(), lat: 47.3769, lon: 8.5417, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Lisboa, PT".into(), lat: 38.7223, lon: -9.1393, tz_offset_minutes: 0 },
|
||||
CityPreset { name: "Dublin, IE".into(), lat: 53.3498, lon: -6.2603, tz_offset_minutes: 0 },
|
||||
CityPreset { name: "Stockholm, SE".into(), lat: 59.3293, lon: 18.0686, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Oslo, NO".into(), lat: 59.9139, lon: 10.7522, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "København, DK".into(), lat: 55.6761, lon: 12.5683, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Helsinki, FI".into(), lat: 60.1699, lon: 24.9384, tz_offset_minutes: 120 },
|
||||
CityPreset { name: "Warszawa, PL".into(), lat: 52.2297, lon: 21.0122, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Praha, CZ".into(), lat: 50.0755, lon: 14.4378, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Budapest, HU".into(), lat: 47.4979, lon: 19.0402, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Athina, GR".into(), lat: 37.9838, lon: 23.7275, tz_offset_minutes: 120 },
|
||||
CityPreset { name: "İstanbul, TR".into(), lat: 41.0082, lon: 28.9784, tz_offset_minutes: 180 },
|
||||
CityPreset { name: "Moskva, RU".into(), lat: 55.7558, lon: 37.6173, tz_offset_minutes: 180 },
|
||||
// USA + Canada
|
||||
CityPreset { name: "New York, US", lat: 40.7128, lon: -74.0060, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Los Angeles, US", lat: 34.0522, lon: -118.2437, tz_offset_minutes: -480 },
|
||||
CityPreset { name: "Chicago, US", lat: 41.8781, lon: -87.6298, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "Miami, US", lat: 25.7617, lon: -80.1918, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Houston, US", lat: 29.7604, lon: -95.3698, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "San Francisco, US", lat: 37.7749, lon: -122.4194, tz_offset_minutes: -480 },
|
||||
CityPreset { name: "Seattle, US", lat: 47.6062, lon: -122.3321, tz_offset_minutes: -480 },
|
||||
CityPreset { name: "Boston, US", lat: 42.3601, lon: -71.0589, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Washington DC", lat: 38.9072, lon: -77.0369, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Toronto, CA", lat: 43.6532, lon: -79.3832, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Montreal, CA", lat: 45.5017, lon: -73.5673, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Vancouver, CA", lat: 49.2827, lon: -123.1207, tz_offset_minutes: -480 },
|
||||
CityPreset { name: "New York, US".into(), lat: 40.7128, lon: -74.0060, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Los Angeles, US".into(), lat: 34.0522, lon: -118.2437, tz_offset_minutes: -480 },
|
||||
CityPreset { name: "Chicago, US".into(), lat: 41.8781, lon: -87.6298, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "Miami, US".into(), lat: 25.7617, lon: -80.1918, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Houston, US".into(), lat: 29.7604, lon: -95.3698, tz_offset_minutes: -360 },
|
||||
CityPreset { name: "San Francisco, US".into(), lat: 37.7749, lon: -122.4194, tz_offset_minutes: -480 },
|
||||
CityPreset { name: "Seattle, US".into(), lat: 47.6062, lon: -122.3321, tz_offset_minutes: -480 },
|
||||
CityPreset { name: "Boston, US".into(), lat: 42.3601, lon: -71.0589, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Washington DC".into(), lat: 38.9072, lon: -77.0369, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Toronto, CA".into(), lat: 43.6532, lon: -79.3832, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Montreal, CA".into(), lat: 45.5017, lon: -73.5673, tz_offset_minutes: -300 },
|
||||
CityPreset { name: "Vancouver, CA".into(), lat: 49.2827, lon: -123.1207, tz_offset_minutes: -480 },
|
||||
// Asia
|
||||
CityPreset { name: "Tokyo, JP", lat: 35.6762, lon: 139.6503, tz_offset_minutes: 540 },
|
||||
CityPreset { name: "Beijing, CN", lat: 39.9042, lon: 116.4074, tz_offset_minutes: 480 },
|
||||
CityPreset { name: "Shanghai, CN", lat: 31.2304, lon: 121.4737, tz_offset_minutes: 480 },
|
||||
CityPreset { name: "Hong Kong", lat: 22.3193, lon: 114.1694, tz_offset_minutes: 480 },
|
||||
CityPreset { name: "Singapore", lat: 1.3521, lon: 103.8198, tz_offset_minutes: 480 },
|
||||
CityPreset { name: "Seoul, KR", lat: 37.5665, lon: 126.9780, tz_offset_minutes: 540 },
|
||||
CityPreset { name: "Bangkok, TH", lat: 13.7563, lon: 100.5018, tz_offset_minutes: 420 },
|
||||
CityPreset { name: "Jakarta, ID", lat: -6.2088, lon: 106.8456, tz_offset_minutes: 420 },
|
||||
CityPreset { name: "Manila, PH", lat: 14.5995, lon: 120.9842, tz_offset_minutes: 480 },
|
||||
CityPreset { name: "Mumbai, IN", lat: 19.0760, lon: 72.8777, tz_offset_minutes: 330 },
|
||||
CityPreset { name: "Delhi, IN", lat: 28.7041, lon: 77.1025, tz_offset_minutes: 330 },
|
||||
CityPreset { name: "Bangalore, IN", lat: 12.9716, lon: 77.5946, tz_offset_minutes: 330 },
|
||||
CityPreset { name: "Karachi, PK", lat: 24.8607, lon: 67.0011, tz_offset_minutes: 300 },
|
||||
CityPreset { name: "Tehran, IR", lat: 35.6892, lon: 51.3890, tz_offset_minutes: 210 },
|
||||
CityPreset { name: "Dubai, AE", lat: 25.2048, lon: 55.2708, tz_offset_minutes: 240 },
|
||||
CityPreset { name: "Tel Aviv, IL", lat: 32.0853, lon: 34.7818, tz_offset_minutes: 120 },
|
||||
CityPreset { name: "Tokyo, JP".into(), lat: 35.6762, lon: 139.6503, tz_offset_minutes: 540 },
|
||||
CityPreset { name: "Beijing, CN".into(), lat: 39.9042, lon: 116.4074, tz_offset_minutes: 480 },
|
||||
CityPreset { name: "Shanghai, CN".into(), lat: 31.2304, lon: 121.4737, tz_offset_minutes: 480 },
|
||||
CityPreset { name: "Hong Kong".into(), lat: 22.3193, lon: 114.1694, tz_offset_minutes: 480 },
|
||||
CityPreset { name: "Singapore".into(), lat: 1.3521, lon: 103.8198, tz_offset_minutes: 480 },
|
||||
CityPreset { name: "Seoul, KR".into(), lat: 37.5665, lon: 126.9780, tz_offset_minutes: 540 },
|
||||
CityPreset { name: "Bangkok, TH".into(), lat: 13.7563, lon: 100.5018, tz_offset_minutes: 420 },
|
||||
CityPreset { name: "Jakarta, ID".into(), lat: -6.2088, lon: 106.8456, tz_offset_minutes: 420 },
|
||||
CityPreset { name: "Manila, PH".into(), lat: 14.5995, lon: 120.9842, tz_offset_minutes: 480 },
|
||||
CityPreset { name: "Mumbai, IN".into(), lat: 19.0760, lon: 72.8777, tz_offset_minutes: 330 },
|
||||
CityPreset { name: "Delhi, IN".into(), lat: 28.7041, lon: 77.1025, tz_offset_minutes: 330 },
|
||||
CityPreset { name: "Bangalore, IN".into(), lat: 12.9716, lon: 77.5946, tz_offset_minutes: 330 },
|
||||
CityPreset { name: "Karachi, PK".into(), lat: 24.8607, lon: 67.0011, tz_offset_minutes: 300 },
|
||||
CityPreset { name: "Tehran, IR".into(), lat: 35.6892, lon: 51.3890, tz_offset_minutes: 210 },
|
||||
CityPreset { name: "Dubai, AE".into(), lat: 25.2048, lon: 55.2708, tz_offset_minutes: 240 },
|
||||
CityPreset { name: "Tel Aviv, IL".into(), lat: 32.0853, lon: 34.7818, tz_offset_minutes: 120 },
|
||||
// África
|
||||
CityPreset { name: "Cairo, EG", lat: 30.0444, lon: 31.2357, tz_offset_minutes: 120 },
|
||||
CityPreset { name: "Lagos, NG", lat: 6.5244, lon: 3.3792, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Nairobi, KE", lat: -1.2921, lon: 36.8219, tz_offset_minutes: 180 },
|
||||
CityPreset { name: "Johannesburg, ZA", lat: -26.2041, lon: 28.0473, tz_offset_minutes: 120 },
|
||||
CityPreset { name: "Cape Town, ZA", lat: -33.9249, lon: 18.4241, tz_offset_minutes: 120 },
|
||||
CityPreset { name: "Casablanca, MA", lat: 33.5731, lon: -7.5898, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Cairo, EG".into(), lat: 30.0444, lon: 31.2357, tz_offset_minutes: 120 },
|
||||
CityPreset { name: "Lagos, NG".into(), lat: 6.5244, lon: 3.3792, tz_offset_minutes: 60 },
|
||||
CityPreset { name: "Nairobi, KE".into(), lat: -1.2921, lon: 36.8219, tz_offset_minutes: 180 },
|
||||
CityPreset { name: "Johannesburg, ZA".into(), lat: -26.2041, lon: 28.0473, tz_offset_minutes: 120 },
|
||||
CityPreset { name: "Cape Town, ZA".into(), lat: -33.9249, lon: 18.4241, tz_offset_minutes: 120 },
|
||||
CityPreset { name: "Casablanca, MA".into(), lat: 33.5731, lon: -7.5898, tz_offset_minutes: 60 },
|
||||
// Oceanía
|
||||
CityPreset { name: "Sydney, AU", lat: -33.8688, lon: 151.2093, tz_offset_minutes: 600 },
|
||||
CityPreset { name: "Melbourne, AU", lat: -37.8136, lon: 144.9631, tz_offset_minutes: 600 },
|
||||
CityPreset { name: "Auckland, NZ", lat: -36.8485, lon: 174.7633, tz_offset_minutes: 720 },
|
||||
];
|
||||
CityPreset { name: "Sydney, AU".into(), lat: -33.8688, lon: 151.2093, tz_offset_minutes: 600 },
|
||||
CityPreset { name: "Melbourne, AU".into(), lat: -37.8136, lon: 144.9631, tz_offset_minutes: 600 },
|
||||
CityPreset { name: "Auckland, NZ".into(), lat: -36.8485, lon: 174.7633, tz_offset_minutes: 720 },
|
||||
]
|
||||
}
|
||||
|
||||
/// Parsea un atlas TSV (tab-separated values): cada línea no vacía y
|
||||
/// no comentario es `name<TAB>lat<TAB>lon<TAB>tz_offset_minutes`.
|
||||
/// Devuelve solo las filas válidas — las inválidas se descartan en
|
||||
/// silencio (no abortamos la carga por una línea mal formada).
|
||||
pub fn parse_city_atlas_tsv(content: &str) -> Vec<CityPreset> {
|
||||
let mut out = Vec::new();
|
||||
for line in content.lines() {
|
||||
let line = line.trim();
|
||||
if line.is_empty() || line.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
let parts: Vec<&str> = line.split('\t').collect();
|
||||
if parts.len() < 4 {
|
||||
continue;
|
||||
}
|
||||
let name = parts[0].trim().to_string();
|
||||
let lat = parts[1].trim().parse::<f64>();
|
||||
let lon = parts[2].trim().parse::<f64>();
|
||||
let tz = parts[3].trim().parse::<i32>();
|
||||
if let (Ok(lat), Ok(lon), Ok(tz)) = (lat, lon, tz) {
|
||||
if !name.is_empty() {
|
||||
out.push(CityPreset {
|
||||
name,
|
||||
lat,
|
||||
lon,
|
||||
tz_offset_minutes: tz,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
impl EventEmitter<TreeEvent> for TahuantinsuyuTree {}
|
||||
|
||||
@@ -292,11 +338,21 @@ impl TahuantinsuyuTree {
|
||||
menu: None,
|
||||
modal: None,
|
||||
city_picker_open: false,
|
||||
city_atlas: default_city_presets(),
|
||||
};
|
||||
me.refresh(cx);
|
||||
me
|
||||
}
|
||||
|
||||
/// Reemplaza el atlas de ciudades del dropdown. La app llama esto
|
||||
/// al boot si encuentra un archivo TSV custom en disco.
|
||||
pub fn set_city_atlas(&mut self, atlas: Vec<CityPreset>, cx: &mut Context<Self>) {
|
||||
if !atlas.is_empty() {
|
||||
self.city_atlas = atlas;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self, cx: &mut Context<Self>) {
|
||||
let mut rows = Vec::new();
|
||||
self.append_groups(None, 0, &mut rows);
|
||||
@@ -430,7 +486,7 @@ impl TahuantinsuyuTree {
|
||||
/// Aplica un city preset al ChartForm activo (CreateChart o
|
||||
/// EditChart). Setea place, lat, lon, tz_offset_min vía
|
||||
/// `TextInput::set_text` y cierra el picker.
|
||||
fn apply_city_preset(&mut self, preset: CityPreset, cx: &mut Context<Self>) {
|
||||
fn apply_city_preset(&mut self, preset: &CityPreset, cx: &mut Context<Self>) {
|
||||
let form = match self.modal.as_mut() {
|
||||
Some(Modal::CreateChart { form, .. }) => form,
|
||||
Some(Modal::EditChart { form, .. }) => form,
|
||||
@@ -444,12 +500,14 @@ impl TahuantinsuyuTree {
|
||||
let lat = form.lat.clone();
|
||||
let lon = form.lon.clone();
|
||||
let tz = form.tz_offset_min.clone();
|
||||
place.update(cx, |i, cx| i.set_text(preset.name.to_string(), cx));
|
||||
lat.update(cx, |i, cx| i.set_text(format!("{}", preset.lat), cx));
|
||||
lon.update(cx, |i, cx| i.set_text(format!("{}", preset.lon), cx));
|
||||
tz.update(cx, |i, cx| {
|
||||
i.set_text(preset.tz_offset_minutes.to_string(), cx)
|
||||
});
|
||||
let name = preset.name.clone();
|
||||
let lat_val = preset.lat;
|
||||
let lon_val = preset.lon;
|
||||
let tz_val = preset.tz_offset_minutes;
|
||||
place.update(cx, |i, cx| i.set_text(name, cx));
|
||||
lat.update(cx, |i, cx| i.set_text(format!("{}", lat_val), cx));
|
||||
lon.update(cx, |i, cx| i.set_text(format!("{}", lon_val), cx));
|
||||
tz.update(cx, |i, cx| i.set_text(tz_val.to_string(), cx));
|
||||
self.city_picker_open = false;
|
||||
cx.notify();
|
||||
}
|
||||
@@ -1299,7 +1357,10 @@ fn render_chart_form(
|
||||
|
||||
// Header del form: title + botón "Ciudad rápida" con dropdown
|
||||
// que autocompleta place/lat/lon/tz al elegir un preset.
|
||||
let picker_open = cx.entity().read(cx).city_picker_open;
|
||||
let (picker_open, atlas_snapshot) = {
|
||||
let me = cx.entity().read(cx);
|
||||
(me.city_picker_open, me.city_atlas.clone())
|
||||
};
|
||||
let city_btn = div()
|
||||
.id("tts-form-city-btn")
|
||||
.px(px(10.0))
|
||||
@@ -1350,9 +1411,10 @@ fn render_chart_form(
|
||||
.flex()
|
||||
.flex_col()
|
||||
.overflow_y_scroll();
|
||||
for preset in CITY_PRESETS.iter().copied() {
|
||||
for preset in atlas_snapshot.iter().cloned() {
|
||||
let row_id: SharedString =
|
||||
SharedString::from(format!("tts-city-{}", preset.name));
|
||||
let label = preset.name.clone();
|
||||
popup = popup.child(
|
||||
div()
|
||||
.id(gpui::ElementId::from(row_id))
|
||||
@@ -1361,9 +1423,9 @@ fn render_chart_form(
|
||||
.text_size(px(11.0))
|
||||
.text_color(theme.fg_text)
|
||||
.hover(|s| s.bg(theme.bg_row_hover))
|
||||
.child(SharedString::from(preset.name.to_string()))
|
||||
.child(SharedString::from(label))
|
||||
.on_click(cx.listener(move |this, _: &ClickEvent, _, cx| {
|
||||
this.apply_city_preset(preset, cx);
|
||||
this.apply_city_preset(&preset, cx);
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user