feat(tahuantinsuyu): fase 17 — filtros de aspectos + editor + cleanup + labels
Fase completa con 4 mejoras independientes que reusan toda la
infraestructura previa:
## A — Filtros de aspectos en NatalModule
NatalModule gana 3 controles nuevos que SÍ recomponen (a diferencia
de los show_* que solo togglean visibilidad):
- Toggle "Mayores (☌ ☍ △ □ ⚹)" default true
- Toggle "Menores (quincunx, semi-…)" default false
- Slider "Multiplicador de orbe" range 0.25..2.5 step 0.25 default 1.0
Engine API extendida sin romper la existente:
- pub struct NatalOptions { show_majors, show_minors, orb_multiplier }
- pub fn compose_with_options(chart, offset, requests, &NatalOptions)
- compose() queda como wrapper con NatalOptions::default()
- bridge::compose acepta el natal_options, construye OrbTable escalada
(build_orb_table multiplier) y filtra aspects antes de pasarlos a
build_render_model. Build_render_model dejó de filtrar majors
internamente — ahora respeta lo que recibe.
Shell wire:
- build_natal_options() lee aspect_majors/aspect_minors/orb_multiplier
desde module_configs["natal"] con defaults seguros.
- on_panel_event para natal: si key empieza con "show_" → canvas
visibility (sin recompose); otherwise → update module_configs +
persist + render_current.
- render_current pasa natal_options a compose_with_options.
## B — Editor de carta natal existente
- Store::update_chart(id, label, &birth, &config) — actualiza tres
columnas preservando id/contact_id/related/created_at_ms y todo el
module_state asociado (la FK CASCADE no se dispara por UPDATE).
- Tree: Modal::EditChart { id, form, error } reusa ChartForm que ya
manejaba el create. open_edit_chart(id, w, cx) lee la carta con
store.get_chart, pre-carga cada TextInput con el valor existente
(label, birthplace, año, mes, día, hora, min, tz, lat, lon, alt).
submit_modal::EditChart lee form, llama update_chart, preserva el
config existente (zodiac/house_system/bodies no se editan acá).
Menú contextual del chart agrega "Editar…" entre "Abrir" y
"Renombrar".
- render_chart_form ahora toma `title: &str` parameter para que el
modal muestre "Editar carta natal" vs "Nueva carta natal". El
botón cambia "Crear carta" → "Guardar cambios" según el title.
## C — Single source of truth para OUTER_RING_MODULES
- engine exporta `pub const OUTER_RING_MODULES: &[&str] = &["transit",
"synastry", "planetary_return"]`
- shell elimina su const local, importa del engine
- canvas elimina 4 listas hardcodeadas (paint_wheel outer ring active
check + glyphs overlay + aspect_endpoints match) y usa contains() o
early-return sobre el slice. Próximo módulo outer-ring solo necesita
agregarse al const, no buscar copias.
## D — Labels ASC/MC/DESC/IC en el perímetro
Cuatro centered_glyphs en radii.sign_outer * 1.06 (justo afuera del
dial zodiacal, dentro del WHEEL_MARGIN) con color angle_highlight y
font 10px. El ojo identifica los 4 ángulos inmediatamente sin tener
que mapear la línea radial gruesa al ángulo correspondiente.
Las posiciones rotan con la rueda (drag del jog-dial los lleva).
`cargo check` y `cargo test` verdes. La fase agregó 6 controles
visibles al panel del NatalModule (4 view + 2 aspect filter + 1
slider) sin tocar la arquitectura de fases 6-15.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,15 @@ enum Modal {
|
||||
form: ChartForm,
|
||||
error: Option<SharedString>,
|
||||
},
|
||||
/// Editar una carta existente — reusa `ChartForm` pre-cargada.
|
||||
/// El submit llama `store.update_chart(id, ...)` preservando
|
||||
/// `chart.contact_id`, `related_chart_id`, `module_state` y el
|
||||
/// historial.
|
||||
EditChart {
|
||||
id: ChartId,
|
||||
form: ChartForm,
|
||||
error: Option<SharedString>,
|
||||
},
|
||||
}
|
||||
|
||||
struct ChartForm {
|
||||
@@ -309,6 +318,58 @@ impl TahuantinsuyuTree {
|
||||
self.close_menu(cx);
|
||||
}
|
||||
|
||||
fn open_edit_chart(
|
||||
&mut self,
|
||||
id: ChartId,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
// Cargar la carta existente; si no se puede, fallamos en silencio.
|
||||
let chart = match self.store.get_chart(id) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("[tree] open_edit_chart {}: {}", id, e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let bd = &chart.birth_data;
|
||||
let form = ChartForm {
|
||||
name: self.make_input("Etiqueta de la carta", &chart.label, window, cx),
|
||||
place: self.make_input(
|
||||
"Lugar (ciudad, país)",
|
||||
bd.birthplace_label.as_deref().unwrap_or(""),
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
year: self.make_input("Año", &bd.year.to_string(), window, cx),
|
||||
month: self.make_input("Mes", &bd.month.to_string(), window, cx),
|
||||
day: self.make_input("Día", &bd.day.to_string(), window, cx),
|
||||
hour: self.make_input("Hora (0-23)", &bd.hour.to_string(), window, cx),
|
||||
minute: self.make_input("Minuto", &bd.minute.to_string(), window, cx),
|
||||
tz_offset_min: self.make_input(
|
||||
"TZ offset (min)",
|
||||
&bd.tz_offset_minutes.to_string(),
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
lat: self.make_input("Latitud (°)", &format!("{}", bd.latitude_deg), window, cx),
|
||||
lon: self.make_input(
|
||||
"Longitud (°)",
|
||||
&format!("{}", bd.longitude_deg),
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
alt: self.make_input("Altitud (m)", &format!("{}", bd.altitude_m), window, cx),
|
||||
};
|
||||
form.name.update(cx, |i, _| i.request_focus(window));
|
||||
self.modal = Some(Modal::EditChart {
|
||||
id,
|
||||
form,
|
||||
error: None,
|
||||
});
|
||||
self.close_menu(cx);
|
||||
}
|
||||
|
||||
fn open_create_chart(
|
||||
&mut self,
|
||||
contact: ContactId,
|
||||
@@ -521,6 +582,56 @@ impl TahuantinsuyuTree {
|
||||
}
|
||||
}
|
||||
}
|
||||
Modal::EditChart {
|
||||
id,
|
||||
form,
|
||||
error: _,
|
||||
} => {
|
||||
let _ = value;
|
||||
// Para preservar el ChartConfig original (zodiac, house
|
||||
// system, bodies, etc.) leemos la carta actual y solo
|
||||
// sobrescribimos label + birth_data. El editor no toca
|
||||
// config — eso se haría en un futuro panel de "Config
|
||||
// de carta".
|
||||
let existing = match self.store.get_chart(id) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
self.modal = Some(Modal::EditChart {
|
||||
id,
|
||||
form,
|
||||
error: Some(SharedString::from(format!("Store: {}", e))),
|
||||
});
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
};
|
||||
match build_chart_from_form(&form, cx) {
|
||||
Ok((birth, label)) => {
|
||||
match self.store.update_chart(id, &label, &birth, &existing.config) {
|
||||
Ok(_) => {
|
||||
drop(form);
|
||||
self.after_mutation(cx);
|
||||
}
|
||||
Err(e) => {
|
||||
self.modal = Some(Modal::EditChart {
|
||||
id,
|
||||
form,
|
||||
error: Some(SharedString::from(format!("Store: {}", e))),
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(msg) => {
|
||||
self.modal = Some(Modal::EditChart {
|
||||
id,
|
||||
form,
|
||||
error: Some(SharedString::from(msg)),
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -829,6 +940,11 @@ impl TahuantinsuyuTree {
|
||||
this.close_menu(cx);
|
||||
}),
|
||||
));
|
||||
items = items.child(menu_item("tts-menu-edit-h", "Editar…", theme).on_click(
|
||||
cx.listener(move |this, _: &ClickEvent, w, cx| {
|
||||
this.open_edit_chart(id, w, cx);
|
||||
}),
|
||||
));
|
||||
items = items.child(separator(theme));
|
||||
let t = menu.target.clone();
|
||||
items = items.child(menu_item("tts-menu-rename-h", "Renombrar…", theme).on_click(
|
||||
@@ -869,7 +985,12 @@ impl TahuantinsuyuTree {
|
||||
input.clone(),
|
||||
"Enter = crear — Escape = cancelar",
|
||||
),
|
||||
Modal::CreateChart { form, error, .. } => render_chart_form(theme, form, error.clone(), cx),
|
||||
Modal::CreateChart { form, error, .. } => {
|
||||
render_chart_form(theme, "Nueva carta natal", form, error.clone(), cx)
|
||||
}
|
||||
Modal::EditChart { form, error, .. } => {
|
||||
render_chart_form(theme, "Editar carta natal", form, error.clone(), cx)
|
||||
}
|
||||
};
|
||||
|
||||
div()
|
||||
@@ -945,6 +1066,7 @@ fn modal_box(
|
||||
|
||||
fn render_chart_form(
|
||||
theme: &Theme,
|
||||
title: &str,
|
||||
form: &ChartForm,
|
||||
error: Option<SharedString>,
|
||||
cx: &mut Context<TahuantinsuyuTree>,
|
||||
@@ -991,11 +1113,15 @@ fn render_chart_form(
|
||||
.hover(|s| s.bg(theme.bg_button_hover()))
|
||||
.text_size(px(12.0))
|
||||
.text_color(theme.fg_text)
|
||||
.child("Crear carta")
|
||||
.child(SharedString::from(if title.starts_with("Editar") {
|
||||
"Guardar cambios"
|
||||
} else {
|
||||
"Crear carta"
|
||||
}))
|
||||
.on_click(cx.listener(|this, _: &ClickEvent, _, cx| {
|
||||
// Disparamos un submit "vacío" — el handler de submit
|
||||
// re-lee todos los campos del form. El value que pasamos
|
||||
// se ignora dentro del branch CreateChart.
|
||||
// se ignora dentro del branch CreateChart/EditChart.
|
||||
this.submit_modal(String::new(), cx);
|
||||
}));
|
||||
|
||||
@@ -1027,7 +1153,7 @@ fn render_chart_form(
|
||||
div()
|
||||
.text_size(px(14.0))
|
||||
.text_color(theme.fg_text)
|
||||
.child("Nueva carta natal"),
|
||||
.child(SharedString::from(title.to_string())),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
|
||||
Reference in New Issue
Block a user