feat(tahuantinsuyu): modal "Guardar como…" real para cartas libres (F3)
Reemplaza el `save_free_chart_quick` MVP de la fase A por un
modal completo que el usuario controla:
Tree:
- Nuevo `Modal::SaveFreeChart { source_id, name, new_contact_name,
selected_contact, all_contacts, error }`.
- `open_save_free_chart_modal` abre el modal pre-poblando `name`
con el label de la carta libre y `selected_contact` con el
primer contacto existente (o `None` = nuevo contacto si no
hay ninguno).
- `gather_all_contacts` recorre la jerarquía recursivamente
devolviendo `(ContactId, "Grupo / Subgrupo / Contacto")` —
el usuario ve la ruta completa, no solo el nombre.
- `render_save_free_chart` pinta:
* Input "Nombre" pre-cargado
* Lista de contactos como botones radio (● / ○) + opción
"Nuevo contacto…" al final
* Si "Nuevo contacto…" seleccionado, aparece input
"Nombre del contacto nuevo"
* Botones Cancelar / Guardar
- `set_save_modal_contact` alterna el radio sin recrear inputs.
- Validaciones: nombre de carta no vacío; si `selected_contact`
es `None`, exigir `new_contact_name` no vacío. Errores se
muestran en una pill destructiva dentro del modal.
- Submit emite nuevo evento `TreeEvent::FreeChartSaveConfirmed
{ source_id, chart_name, contact, new_contact_name }`.
Shell:
- `persist_free_chart` resuelve el contacto destino (existente
o crea uno nuevo), llama `store.create_chart`, y al éxito
remueve la carta libre del mapa (salvo `sky-now`, que es
persistente). Si la carta libre estaba seleccionada, vuelve
al Cielo. Refresca opciones del picker para que el dropdown
ChartPicker incluya la carta recién guardada.
- El handler `SaveFreeChartRequested` queda como hook vacío;
el menú del tree abre el modal directamente con `window`.
10 tests verdes (no se afectaron los paths probados).
Próximo: F2 (editor inline de fecha/lugar/hora de la carta
libre) y F4 (botón "Guardar como…" en cada módulo overlay
con sufijo automático).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -481,12 +481,25 @@ impl Shell {
|
|||||||
self.apply_selection(TreeSelection::FreeChart(id), cx);
|
self.apply_selection(TreeSelection::FreeChart(id), cx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TreeEvent::SaveFreeChartRequested(id) => {
|
TreeEvent::SaveFreeChartRequested(_id) => {
|
||||||
// Por ahora: log + persistencia simple en "General"
|
// El menú del tree abre el modal directamente; este
|
||||||
// (contact nuevo con el label de la carta). En la
|
// evento queda como hook por si una tecla u otra
|
||||||
// fase B se reemplaza por un modal con dropdown de
|
// UI quiere disparar el flujo sin pasar por el menú.
|
||||||
// contacto y custom name.
|
return;
|
||||||
self.save_free_chart_quick(id.clone());
|
}
|
||||||
|
TreeEvent::FreeChartSaveConfirmed {
|
||||||
|
source_id,
|
||||||
|
chart_name,
|
||||||
|
contact,
|
||||||
|
new_contact_name,
|
||||||
|
} => {
|
||||||
|
self.persist_free_chart(
|
||||||
|
source_id.clone(),
|
||||||
|
chart_name.clone(),
|
||||||
|
contact.clone(),
|
||||||
|
new_contact_name.clone(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TreeEvent::DeleteFreeChartRequested(id) => {
|
TreeEvent::DeleteFreeChartRequested(id) => {
|
||||||
@@ -510,41 +523,71 @@ impl Shell {
|
|||||||
self.apply_selection(selection.clone(), cx);
|
self.apply_selection(selection.clone(), cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Guardado rápido de una carta libre como carta natal bajo un
|
/// Persiste una carta libre como `Chart` en la store. El usuario
|
||||||
/// contacto nuevo (auto-nombrado con el label de la carta libre).
|
/// eligió en el modal: nombre + contacto destino (existente o
|
||||||
/// Es la versión MVP — en la fase B se reemplaza por un modal
|
/// uno nuevo creado al vuelo). La carta libre se REMUEVE del
|
||||||
/// con dropdown de contacto + input de nombre custom.
|
/// mapa tras el persist exitoso — si quedaba seleccionada,
|
||||||
fn save_free_chart_quick(&mut self, id: FreeChartId) {
|
/// volvemos a "Cielo ahora". Si falla la persistencia, la carta
|
||||||
let Some(chart) = self.free_charts.get(&id).cloned() else {
|
/// libre se conserva y logueamos.
|
||||||
|
fn persist_free_chart(
|
||||||
|
&mut self,
|
||||||
|
source_id: FreeChartId,
|
||||||
|
chart_name: String,
|
||||||
|
contact: Option<ContactId>,
|
||||||
|
new_contact_name: Option<String>,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let Some(chart) = self.free_charts.get(&source_id).cloned() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// 1) Crear o reusar un contacto. Por simplicidad, creamos uno
|
// 1) Resolver el contact destino (existente o crear nuevo).
|
||||||
// nuevo con el label de la carta como nombre. La fase B le
|
let contact_id = match (contact, new_contact_name) {
|
||||||
// dará al usuario el dropdown.
|
(Some(cid), _) => cid,
|
||||||
let contact = match self.store.create_contact(None, &chart.label, None) {
|
(None, Some(name)) => match self.store.create_contact(None, &name, None) {
|
||||||
Ok(c) => c,
|
Ok(c) => c.id,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("[shell] create_contact al guardar libre: {}", e);
|
eprintln!("[shell] create_contact al guardar libre: {}", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
(None, None) => {
|
||||||
|
eprintln!("[shell] persist_free_chart sin contacto ni nombre nuevo");
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// 2) Crear la carta bajo ese contacto.
|
// 2) Crear la carta.
|
||||||
if let Err(e) = self.store.create_chart(
|
match self.store.create_chart(
|
||||||
contact.id,
|
contact_id,
|
||||||
chart.kind,
|
chart.kind,
|
||||||
&chart.label,
|
&chart_name,
|
||||||
&chart.birth_data,
|
&chart.birth_data,
|
||||||
&chart.config,
|
&chart.config,
|
||||||
chart.related_chart_id,
|
chart.related_chart_id,
|
||||||
) {
|
) {
|
||||||
|
Ok(_) => {
|
||||||
|
eprintln!(
|
||||||
|
"[shell] carta libre {:?} guardada como '{}' bajo contacto {}",
|
||||||
|
source_id, chart_name, contact_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
eprintln!("[shell] create_chart al guardar libre: {}", e);
|
eprintln!("[shell] create_chart al guardar libre: {}", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
eprintln!(
|
}
|
||||||
"[shell] carta libre '{}' guardada bajo contacto '{}' (id {})",
|
// 3) Sky-now se conserva (siempre es); las demás se quitan
|
||||||
chart.label, contact.name, contact.id
|
// del mapa libre. Si era la activa, volver al Cielo.
|
||||||
|
if !source_id.is_sky_now() {
|
||||||
|
self.free_charts.remove(&source_id);
|
||||||
|
self.push_free_charts_to_tree(cx);
|
||||||
|
// Si la activa era esta libre, regresar al Cielo.
|
||||||
|
self.apply_selection(
|
||||||
|
TreeSelection::FreeChart(FreeChartId::sky_now()),
|
||||||
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
self.refresh_chart_options(cx);
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_selection(&mut self, sel: TreeSelection, cx: &mut Context<Self>) {
|
fn apply_selection(&mut self, sel: TreeSelection, cx: &mut Context<Self>) {
|
||||||
match sel {
|
match sel {
|
||||||
|
|||||||
@@ -70,6 +70,15 @@ pub enum TreeEvent {
|
|||||||
/// Borrar una carta libre del mapa del shell. Si es `sky-now`,
|
/// Borrar una carta libre del mapa del shell. Si es `sky-now`,
|
||||||
/// el shell ignora (no se puede borrar el Cielo).
|
/// el shell ignora (no se puede borrar el Cielo).
|
||||||
DeleteFreeChartRequested(FreeChartId),
|
DeleteFreeChartRequested(FreeChartId),
|
||||||
|
/// Submit del modal "Guardar como" — el shell crea/usa el
|
||||||
|
/// contacto y persiste la carta. Si `contact` es `None`, el
|
||||||
|
/// shell crea uno nuevo con `new_contact_name`.
|
||||||
|
FreeChartSaveConfirmed {
|
||||||
|
source_id: FreeChartId,
|
||||||
|
chart_name: String,
|
||||||
|
contact: Option<ContactId>,
|
||||||
|
new_contact_name: Option<String>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
@@ -148,6 +157,25 @@ enum Modal {
|
|||||||
form: ChartForm,
|
form: ChartForm,
|
||||||
error: Option<SharedString>,
|
error: Option<SharedString>,
|
||||||
},
|
},
|
||||||
|
/// Guardar una carta libre como `Chart` persistido. El usuario
|
||||||
|
/// elige nombre + contacto destino (existente de la lista o
|
||||||
|
/// uno nuevo creado al vuelo). El shell escucha
|
||||||
|
/// `TreeEvent::FreeChartSaveConfirmed` y materializa.
|
||||||
|
SaveFreeChart {
|
||||||
|
source_id: FreeChartId,
|
||||||
|
name: Entity<TextInput>,
|
||||||
|
/// Nombre del contacto NUEVO (solo aplica si
|
||||||
|
/// `selected_contact == None`). Vacío para reusar uno
|
||||||
|
/// existente.
|
||||||
|
new_contact_name: Entity<TextInput>,
|
||||||
|
/// `Some(id)` = usar contacto existente; `None` = crear
|
||||||
|
/// contacto nuevo con `new_contact_name`.
|
||||||
|
selected_contact: Option<ContactId>,
|
||||||
|
/// Snapshot de contactos visibles al usuario en el momento
|
||||||
|
/// de abrir el modal. Incluye contact id + label (nombre).
|
||||||
|
all_contacts: Vec<(ContactId, String)>,
|
||||||
|
error: Option<SharedString>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ChartForm {
|
struct ChartForm {
|
||||||
@@ -845,6 +873,101 @@ impl TahuantinsuyuTree {
|
|||||||
self.close_menu(cx);
|
self.close_menu(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Abre el modal "Guardar como" para una carta libre. Pre-puebla
|
||||||
|
/// el `name` con el label actual de la entry. La lista de
|
||||||
|
/// contactos es un snapshot recursivo de toda la jerarquía
|
||||||
|
/// (no solo el nivel raíz). El usuario elige uno existente o
|
||||||
|
/// deja en "Nuevo contacto" para que se cree uno al confirmar.
|
||||||
|
/// Cambia `selected_contact` del modal `SaveFreeChart` activo
|
||||||
|
/// sin recrear los inputs. Permite alternar entre los botones
|
||||||
|
/// radio "contacto existente" y "Nuevo contacto…".
|
||||||
|
fn set_save_modal_contact(
|
||||||
|
&mut self,
|
||||||
|
new_selection: Option<ContactId>,
|
||||||
|
expected_source: &FreeChartId,
|
||||||
|
cx: &mut Context<'_, Self>,
|
||||||
|
) {
|
||||||
|
if let Some(Modal::SaveFreeChart {
|
||||||
|
source_id,
|
||||||
|
selected_contact,
|
||||||
|
..
|
||||||
|
}) = self.modal.as_mut()
|
||||||
|
{
|
||||||
|
if source_id == expected_source {
|
||||||
|
*selected_contact = new_selection;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_save_free_chart_modal(
|
||||||
|
&mut self,
|
||||||
|
source_id: FreeChartId,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<'_, Self>,
|
||||||
|
) {
|
||||||
|
let default_label = self
|
||||||
|
.free_charts
|
||||||
|
.iter()
|
||||||
|
.find(|e| e.id == source_id)
|
||||||
|
.map(|e| e.label.clone())
|
||||||
|
.unwrap_or_else(|| "Carta libre".into());
|
||||||
|
let all_contacts = self.gather_all_contacts();
|
||||||
|
let name_input = self.make_input("Nombre de la carta", &default_label, window, cx);
|
||||||
|
let new_contact_input =
|
||||||
|
self.make_input("Nombre del contacto nuevo", "", window, cx);
|
||||||
|
// Default: primer contacto existente si lo hay; sino "Nuevo
|
||||||
|
// contacto" (None). Eso minimiza clicks cuando el usuario ya
|
||||||
|
// tiene contactos cargados.
|
||||||
|
let selected_contact = all_contacts.first().map(|(id, _)| *id);
|
||||||
|
self.modal = Some(Modal::SaveFreeChart {
|
||||||
|
source_id,
|
||||||
|
name: name_input,
|
||||||
|
new_contact_name: new_contact_input,
|
||||||
|
selected_contact,
|
||||||
|
all_contacts,
|
||||||
|
error: None,
|
||||||
|
});
|
||||||
|
self.close_menu(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Snapshot recursivo de todos los contactos del árbol —
|
||||||
|
/// `(id, label)`. Usado por el modal "Guardar como" para
|
||||||
|
/// listar destinos. Las cartas se cuelgan del contacto que el
|
||||||
|
/// usuario elija.
|
||||||
|
fn gather_all_contacts(&self) -> Vec<(ContactId, String)> {
|
||||||
|
fn walk(
|
||||||
|
store: &Store,
|
||||||
|
parent: Option<GroupId>,
|
||||||
|
prefix: &str,
|
||||||
|
out: &mut Vec<(ContactId, String)>,
|
||||||
|
) {
|
||||||
|
if let Ok(contacts) = store.list_contacts(parent) {
|
||||||
|
for c in contacts {
|
||||||
|
let label = if prefix.is_empty() {
|
||||||
|
c.name.clone()
|
||||||
|
} else {
|
||||||
|
format!("{}{}", prefix, c.name)
|
||||||
|
};
|
||||||
|
out.push((c.id, label));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(groups) = store.list_groups(parent) {
|
||||||
|
for g in groups {
|
||||||
|
let new_prefix = if prefix.is_empty() {
|
||||||
|
format!("{} / ", g.name)
|
||||||
|
} else {
|
||||||
|
format!("{}{} / ", prefix, g.name)
|
||||||
|
};
|
||||||
|
walk(store, Some(g.id), &new_prefix, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut out = Vec::new();
|
||||||
|
walk(&self.store, None, "", &mut out);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
fn open_create_chart(
|
fn open_create_chart(
|
||||||
&mut self,
|
&mut self,
|
||||||
contact: ContactId,
|
contact: ContactId,
|
||||||
@@ -1107,6 +1230,62 @@ impl TahuantinsuyuTree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Modal::SaveFreeChart {
|
||||||
|
source_id,
|
||||||
|
name,
|
||||||
|
new_contact_name,
|
||||||
|
selected_contact,
|
||||||
|
all_contacts,
|
||||||
|
error: _,
|
||||||
|
} => {
|
||||||
|
let _ = value;
|
||||||
|
let chart_name = name.read(cx).text().to_string();
|
||||||
|
let chart_name = chart_name.trim();
|
||||||
|
if chart_name.is_empty() {
|
||||||
|
self.modal = Some(Modal::SaveFreeChart {
|
||||||
|
source_id,
|
||||||
|
name,
|
||||||
|
new_contact_name,
|
||||||
|
selected_contact,
|
||||||
|
all_contacts,
|
||||||
|
error: Some("El nombre de la carta no puede estar vacío".into()),
|
||||||
|
});
|
||||||
|
cx.notify();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let new_contact = if selected_contact.is_none() {
|
||||||
|
let v = new_contact_name.read(cx).text().to_string();
|
||||||
|
let v = v.trim();
|
||||||
|
if v.is_empty() {
|
||||||
|
self.modal = Some(Modal::SaveFreeChart {
|
||||||
|
source_id,
|
||||||
|
name,
|
||||||
|
new_contact_name,
|
||||||
|
selected_contact,
|
||||||
|
all_contacts,
|
||||||
|
error: Some(
|
||||||
|
"Elegí un contacto existente o escribí un nombre para el nuevo"
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
cx.notify();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Some(v.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
cx.emit(TreeEvent::FreeChartSaveConfirmed {
|
||||||
|
source_id,
|
||||||
|
chart_name: chart_name.to_string(),
|
||||||
|
contact: selected_contact,
|
||||||
|
new_contact_name: new_contact,
|
||||||
|
});
|
||||||
|
drop(name);
|
||||||
|
drop(new_contact_name);
|
||||||
|
self.modal = None;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1441,9 +1620,8 @@ impl TahuantinsuyuTree {
|
|||||||
let fid_save = fid.clone();
|
let fid_save = fid.clone();
|
||||||
items = items.child(
|
items = items.child(
|
||||||
menu_item("tts-menu-save-free", "Guardar como…", theme).on_click(
|
menu_item("tts-menu-save-free", "Guardar como…", theme).on_click(
|
||||||
cx.listener(move |this, _: &ClickEvent, _w, cx| {
|
cx.listener(move |this, _: &ClickEvent, w, cx| {
|
||||||
cx.emit(TreeEvent::SaveFreeChartRequested(fid_save.clone()));
|
this.open_save_free_chart_modal(fid_save.clone(), w, cx);
|
||||||
this.close_menu(cx);
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1530,6 +1708,23 @@ impl TahuantinsuyuTree {
|
|||||||
&self.city_atlas,
|
&self.city_atlas,
|
||||||
cx,
|
cx,
|
||||||
),
|
),
|
||||||
|
Modal::SaveFreeChart {
|
||||||
|
source_id,
|
||||||
|
name,
|
||||||
|
new_contact_name,
|
||||||
|
selected_contact,
|
||||||
|
all_contacts,
|
||||||
|
error,
|
||||||
|
} => render_save_free_chart(
|
||||||
|
theme,
|
||||||
|
source_id.clone(),
|
||||||
|
name.clone(),
|
||||||
|
new_contact_name.clone(),
|
||||||
|
*selected_contact,
|
||||||
|
all_contacts,
|
||||||
|
error.clone(),
|
||||||
|
cx,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
div()
|
div()
|
||||||
@@ -1603,6 +1798,184 @@ fn modal_box(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Modal "Guardar como" para una carta libre. Layout:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// [Nombre de la carta] — TextInput pre-poblado con label
|
||||||
|
/// Contacto destino:
|
||||||
|
/// ○ Contacto A
|
||||||
|
/// ○ Contacto B
|
||||||
|
/// ● Nuevo contacto… → [Nombre del contacto] TextInput
|
||||||
|
/// [Cancelar] [Guardar]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// El submit emite `TreeEvent::FreeChartSaveConfirmed` que el shell
|
||||||
|
/// materializa contra la store.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn render_save_free_chart(
|
||||||
|
theme: &Theme,
|
||||||
|
source_id: FreeChartId,
|
||||||
|
name: Entity<TextInput>,
|
||||||
|
new_contact_name: Entity<TextInput>,
|
||||||
|
selected_contact: Option<ContactId>,
|
||||||
|
all_contacts: &[(ContactId, String)],
|
||||||
|
error: Option<SharedString>,
|
||||||
|
cx: &mut Context<'_, TahuantinsuyuTree>,
|
||||||
|
) -> gpui::Div {
|
||||||
|
let title_row = div()
|
||||||
|
.text_size(px(14.0))
|
||||||
|
.text_color(theme.fg_text)
|
||||||
|
.child(SharedString::from("Guardar carta libre"));
|
||||||
|
let label_row =
|
||||||
|
|label: &'static str| -> gpui::Div {
|
||||||
|
div()
|
||||||
|
.text_size(px(10.0))
|
||||||
|
.text_color(theme.fg_muted)
|
||||||
|
.child(label)
|
||||||
|
};
|
||||||
|
let name_block = div()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap(px(2.0))
|
||||||
|
.child(label_row("Nombre"))
|
||||||
|
.child(name.clone());
|
||||||
|
|
||||||
|
// Lista de contactos como botones radio.
|
||||||
|
let mut contact_list = div().flex().flex_col().gap(px(4.0));
|
||||||
|
for (cid, label) in all_contacts.iter() {
|
||||||
|
let selected = selected_contact == Some(*cid);
|
||||||
|
let cid_for_click = *cid;
|
||||||
|
let source_for_click = source_id.clone();
|
||||||
|
let row_id: SharedString =
|
||||||
|
SharedString::from(format!("tts-save-pick-{}", cid));
|
||||||
|
let bullet = if selected { "●" } else { "○" };
|
||||||
|
let row = div()
|
||||||
|
.id(gpui::ElementId::from(row_id))
|
||||||
|
.flex()
|
||||||
|
.flex_row()
|
||||||
|
.gap(px(8.0))
|
||||||
|
.px(px(6.0))
|
||||||
|
.py(px(3.0))
|
||||||
|
.rounded(px(4.0))
|
||||||
|
.text_size(px(11.0))
|
||||||
|
.text_color(theme.fg_text)
|
||||||
|
.hover(|s| s.bg(theme.bg_row_hover))
|
||||||
|
.child(bullet.to_string())
|
||||||
|
.child(label.clone())
|
||||||
|
.on_click(cx.listener(move |this, _: &ClickEvent, _, cx| {
|
||||||
|
this.set_save_modal_contact(Some(cid_for_click), &source_for_click, cx);
|
||||||
|
}));
|
||||||
|
contact_list = contact_list.child(row);
|
||||||
|
}
|
||||||
|
// Opción "Nuevo contacto…" — bullet activo si selected_contact==None.
|
||||||
|
let new_selected = selected_contact.is_none();
|
||||||
|
let new_bullet = if new_selected { "●" } else { "○" };
|
||||||
|
let source_for_new = source_id.clone();
|
||||||
|
contact_list = contact_list.child(
|
||||||
|
div()
|
||||||
|
.id(gpui::ElementId::from(SharedString::from(
|
||||||
|
"tts-save-pick-new",
|
||||||
|
)))
|
||||||
|
.flex()
|
||||||
|
.flex_row()
|
||||||
|
.gap(px(8.0))
|
||||||
|
.px(px(6.0))
|
||||||
|
.py(px(3.0))
|
||||||
|
.rounded(px(4.0))
|
||||||
|
.text_size(px(11.0))
|
||||||
|
.text_color(theme.fg_text)
|
||||||
|
.hover(|s| s.bg(theme.bg_row_hover))
|
||||||
|
.child(new_bullet.to_string())
|
||||||
|
.child("Nuevo contacto…")
|
||||||
|
.on_click(cx.listener(move |this, _: &ClickEvent, _, cx| {
|
||||||
|
this.set_save_modal_contact(None, &source_for_new, cx);
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut contacts_block = div()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap(px(2.0))
|
||||||
|
.child(label_row("Contacto destino"))
|
||||||
|
.child(contact_list);
|
||||||
|
|
||||||
|
// Si "Nuevo contacto" está activo, mostrar el TextInput debajo.
|
||||||
|
if new_selected {
|
||||||
|
contacts_block = contacts_block.child(
|
||||||
|
div()
|
||||||
|
.pt(px(4.0))
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap(px(2.0))
|
||||||
|
.child(label_row("Nombre del contacto nuevo"))
|
||||||
|
.child(new_contact_name.clone()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let save_btn = div()
|
||||||
|
.id("tts-save-free-confirm")
|
||||||
|
.px(px(14.0))
|
||||||
|
.py(px(8.0))
|
||||||
|
.rounded(px(6.0))
|
||||||
|
.bg(theme.bg_button())
|
||||||
|
.hover(|s| s.bg(theme.bg_button_hover()))
|
||||||
|
.text_size(px(12.0))
|
||||||
|
.text_color(theme.fg_text)
|
||||||
|
.child("Guardar")
|
||||||
|
.on_click(cx.listener(|this, _: &ClickEvent, _, cx| {
|
||||||
|
this.submit_modal(String::new(), cx);
|
||||||
|
}));
|
||||||
|
let cancel_btn = div()
|
||||||
|
.id("tts-save-free-cancel")
|
||||||
|
.px(px(14.0))
|
||||||
|
.py(px(8.0))
|
||||||
|
.rounded(px(6.0))
|
||||||
|
.bg(theme.bg_panel.clone())
|
||||||
|
.hover(|s| s.bg(theme.bg_row_hover))
|
||||||
|
.text_size(px(12.0))
|
||||||
|
.text_color(theme.fg_muted)
|
||||||
|
.child("Cancelar")
|
||||||
|
.on_click(cx.listener(|this, _: &ClickEvent, _, cx| {
|
||||||
|
this.close_modal(cx);
|
||||||
|
}));
|
||||||
|
|
||||||
|
let mut body = div()
|
||||||
|
.min_w(px(420.0))
|
||||||
|
.p(px(18.0))
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap(px(12.0))
|
||||||
|
.bg(theme.bg_panel_alt.clone())
|
||||||
|
.border_1()
|
||||||
|
.border_color(theme.border_strong)
|
||||||
|
.rounded(px(8.0))
|
||||||
|
.child(title_row)
|
||||||
|
.child(name_block)
|
||||||
|
.child(contacts_block);
|
||||||
|
if let Some(err) = error {
|
||||||
|
body = body.child(
|
||||||
|
div()
|
||||||
|
.px(px(10.0))
|
||||||
|
.py(px(6.0))
|
||||||
|
.rounded(px(4.0))
|
||||||
|
.bg(theme.bg_destructive_hover())
|
||||||
|
.text_size(px(11.0))
|
||||||
|
.text_color(theme.accent_destructive())
|
||||||
|
.child(err),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
body = body.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_row()
|
||||||
|
.gap(px(8.0))
|
||||||
|
.justify_end()
|
||||||
|
.child(cancel_btn)
|
||||||
|
.child(save_btn),
|
||||||
|
);
|
||||||
|
body
|
||||||
|
}
|
||||||
|
|
||||||
fn render_chart_form(
|
fn render_chart_form(
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
title: &str,
|
title: &str,
|
||||||
|
|||||||
Reference in New Issue
Block a user