Implement control and display of tab splits.
This commit is contained in:
parent
0da685d4f5
commit
6dd6cd1f71
@ -208,9 +208,11 @@ pub fn install_lua_globals(
|
|||||||
}
|
}
|
||||||
register_stateless_command!(mud_trigger, "act");
|
register_stateless_command!(mud_trigger, "act");
|
||||||
register_stateless_command!(mud_trigger, "action");
|
register_stateless_command!(mud_trigger, "action");
|
||||||
|
register_command!(add_tab);
|
||||||
register_stateless_command!(alias);
|
register_stateless_command!(alias);
|
||||||
register_stateless_command!(unalias);
|
register_stateless_command!(unalias);
|
||||||
register_command!(close_mud);
|
register_command!(close_mud);
|
||||||
|
register_command!(close_tab);
|
||||||
register_command!(command);
|
register_command!(command);
|
||||||
register_command!(connect_mud);
|
register_command!(connect_mud);
|
||||||
register_stateless_command!(create_match_table);
|
register_stateless_command!(create_match_table);
|
||||||
@ -234,6 +236,7 @@ pub fn install_lua_globals(
|
|||||||
register_command!(sendmud_raw);
|
register_command!(sendmud_raw);
|
||||||
register_stateless_command!(storage);
|
register_stateless_command!(storage);
|
||||||
register_command!(tick);
|
register_command!(tick);
|
||||||
|
register_command!(tsplit);
|
||||||
register_stateless_command!(mud_trigger, "untrigger");
|
register_stateless_command!(mud_trigger, "untrigger");
|
||||||
register_stateless_command!(mud_untrigger, "unact");
|
register_stateless_command!(mud_untrigger, "unact");
|
||||||
register_stateless_command!(mud_untrigger, "unaction");
|
register_stateless_command!(mud_untrigger, "unaction");
|
||||||
|
@ -265,6 +265,99 @@ pub fn hsplit<'gc>(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tsplit<'gc>(
|
||||||
|
ctx: Context<'gc>,
|
||||||
|
global_memo: &GlobalMemoCell,
|
||||||
|
global_layout: &UseStateSetter<GlobalLayoutCell>,
|
||||||
|
) -> Callback<'gc> {
|
||||||
|
let global_layout = global_layout.clone();
|
||||||
|
let global_memo = global_memo.clone();
|
||||||
|
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||||
|
let tabid: String = stack.from_front(ctx)?;
|
||||||
|
let path: String = stack.from_front(ctx)?;
|
||||||
|
if tabid.len() != 1 {
|
||||||
|
Err(anyhow::Error::msg(
|
||||||
|
"Tab identifier should be a single character".to_owned(),
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
let tabid = tabid.chars().next().unwrap();
|
||||||
|
let new_splits = global_memo
|
||||||
|
.layout
|
||||||
|
.borrow()
|
||||||
|
.term_splits
|
||||||
|
.tabbed(tabid, &path)
|
||||||
|
.map_err(|e| e.into_value(ctx))?;
|
||||||
|
let new_layout = Rc::new(GlobalLayoutState {
|
||||||
|
term_splits: new_splits.clone(),
|
||||||
|
..(*(global_memo.layout.borrow().as_ref())).clone()
|
||||||
|
});
|
||||||
|
global_layout.set(new_layout.clone());
|
||||||
|
*(global_memo.layout.borrow_mut()) = new_layout;
|
||||||
|
Ok(CallbackReturn::Return)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_tab<'gc>(
|
||||||
|
ctx: Context<'gc>,
|
||||||
|
global_memo: &GlobalMemoCell,
|
||||||
|
global_layout: &UseStateSetter<GlobalLayoutCell>,
|
||||||
|
) -> Callback<'gc> {
|
||||||
|
let global_layout = global_layout.clone();
|
||||||
|
let global_memo = global_memo.clone();
|
||||||
|
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||||
|
let path: String = stack.from_front(ctx)?;
|
||||||
|
let tabid: String = stack.from_front(ctx)?;
|
||||||
|
if tabid.len() != 1 {
|
||||||
|
Err(anyhow::Error::msg(
|
||||||
|
"Tab identifier should be a single character".to_owned(),
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
let tabid = tabid.chars().next().unwrap();
|
||||||
|
let frame: FrameId = FrameId(stack.from_front(ctx)?);
|
||||||
|
let new_splits = global_memo
|
||||||
|
.layout
|
||||||
|
.borrow()
|
||||||
|
.term_splits
|
||||||
|
.add_tab(&path, tabid, frame.clone())
|
||||||
|
.map_err(|e| e.into_value(ctx))?;
|
||||||
|
let new_layout = Rc::new(GlobalLayoutState {
|
||||||
|
term_splits: new_splits.clone(),
|
||||||
|
..(*(global_memo.layout.borrow().as_ref())).clone()
|
||||||
|
});
|
||||||
|
global_layout.set(new_layout.clone());
|
||||||
|
*(global_memo.layout.borrow_mut()) = new_layout;
|
||||||
|
Ok(CallbackReturn::Call {
|
||||||
|
function: Function::Callback(ensure_frame_instance(ctx, &frame)),
|
||||||
|
then: None,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close_tab<'gc>(
|
||||||
|
ctx: Context<'gc>,
|
||||||
|
global_memo: &GlobalMemoCell,
|
||||||
|
global_layout: &UseStateSetter<GlobalLayoutCell>,
|
||||||
|
) -> Callback<'gc> {
|
||||||
|
let global_layout = global_layout.clone();
|
||||||
|
let global_memo = global_memo.clone();
|
||||||
|
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||||
|
let path: String = stack.from_front(ctx)?;
|
||||||
|
let new_splits = global_memo
|
||||||
|
.layout
|
||||||
|
.borrow()
|
||||||
|
.term_splits
|
||||||
|
.close_tab(&path)
|
||||||
|
.map_err(|e| e.into_value(ctx))?;
|
||||||
|
let new_layout = Rc::new(GlobalLayoutState {
|
||||||
|
term_splits: new_splits.clone(),
|
||||||
|
..(*(global_memo.layout.borrow().as_ref())).clone()
|
||||||
|
});
|
||||||
|
global_layout.set(new_layout.clone());
|
||||||
|
*(global_memo.layout.borrow_mut()) = new_layout;
|
||||||
|
Ok(CallbackReturn::Return)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn panel_merge<'gc>(
|
pub fn panel_merge<'gc>(
|
||||||
ctx: Context<'gc>,
|
ctx: Context<'gc>,
|
||||||
global_memo: &GlobalMemoCell,
|
global_memo: &GlobalMemoCell,
|
||||||
|
@ -7,11 +7,14 @@ pub struct TabPanelProps {
|
|||||||
|
|
||||||
#[function_component(TabPanel)]
|
#[function_component(TabPanel)]
|
||||||
pub fn tab_panel_props(props: &TabPanelProps) -> Html {
|
pub fn tab_panel_props(props: &TabPanelProps) -> Html {
|
||||||
let active_tab = use_state_eq(|| props.tabs.get(0).map(|(k, _)| *k).unwrap_or('0'));
|
let active_tab = use_state_eq(|| props.tabs.first().map(|(k, _)| *k).unwrap_or('0'));
|
||||||
html! {
|
html! {
|
||||||
<div class="container-fluid d-flex flex-column">
|
<div class="d-flex flex-column h-100">
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
{props.tabs.iter().map(|(c, _)| {
|
{props.tabs.iter().map(|(c, _)| {
|
||||||
|
if !props.tabs.is_empty() && !props.tabs.iter().any(|t| t.0 == *active_tab) {
|
||||||
|
active_tab.set(props.tabs.first().map(|v| v.0).unwrap_or('0'));
|
||||||
|
}
|
||||||
let class_name = if *c == *active_tab { "nav-link active" } else { "nav-link" };
|
let class_name = if *c == *active_tab { "nav-link active" } else { "nav-link" };
|
||||||
let c_click: char = *c;
|
let c_click: char = *c;
|
||||||
let active_tab_click = active_tab.clone();
|
let active_tab_click = active_tab.clone();
|
||||||
@ -26,7 +29,7 @@ pub fn tab_panel_props(props: &TabPanelProps) -> Html {
|
|||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
{props.tabs.iter().map(|(c, v)| {
|
{props.tabs.iter().map(|(c, v)| {
|
||||||
let class_name = if *c == *active_tab { "container-fluid visible"} else { "container-fluid invisible"};
|
let class_name = if *c == *active_tab { "termtreewrapper"} else { "d-none"};
|
||||||
html! {
|
html! {
|
||||||
<div class={class_name} key={format!("content-{}", c)}>
|
<div class={class_name} key={format!("content-{}", c)}>
|
||||||
{v.clone()}
|
{v.clone()}
|
||||||
|
@ -236,6 +236,36 @@ impl TermSplit {
|
|||||||
new.validate()?;
|
new.validate()?;
|
||||||
Ok(new)
|
Ok(new)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn close_tab(&self, pathstr: &str) -> Result<TermSplit, String> {
|
||||||
|
if pathstr.is_empty() {
|
||||||
|
return Err("Closing tab is invalid with empty path".to_owned());
|
||||||
|
}
|
||||||
|
let (head, last) = pathstr.split_at(pathstr.len() - 1);
|
||||||
|
let last: char = last.chars().next().unwrap();
|
||||||
|
|
||||||
|
let new = self.modify_at_pathstr(head, |t| match t {
|
||||||
|
TermSplit::Tabs { tabs } => {
|
||||||
|
let tabs_with_close = tabs.without(&last);
|
||||||
|
if tabs_with_close.is_empty() {
|
||||||
|
Err(format!(
|
||||||
|
"Tried to close tab {} at path {}, but that would leave the tab set empty.",
|
||||||
|
last, head
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(TermSplit::Tabs {
|
||||||
|
tabs: tabs_with_close,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(format!(
|
||||||
|
"Tried to close tab at path {}, which isn't a tab set.",
|
||||||
|
pathstr
|
||||||
|
)),
|
||||||
|
})?;
|
||||||
|
new.validate()?;
|
||||||
|
Ok(new)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AccessibleSplitIter<'t> {
|
pub struct AccessibleSplitIter<'t> {
|
||||||
@ -540,4 +570,54 @@ mod tests {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_close_tab() {
|
||||||
|
use TermSplit::*;
|
||||||
|
let t = Vertical {
|
||||||
|
top: Horizontal {
|
||||||
|
left: Horizontal {
|
||||||
|
left: Term { frame: FrameId(42) }.into(),
|
||||||
|
right: Term { frame: FrameId(43) }.into(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
right: Term { frame: FrameId(44) }.into(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
bottom: Vertical {
|
||||||
|
top: Term { frame: FrameId(45) }.into(),
|
||||||
|
bottom: Tabs {
|
||||||
|
tabs: vec![
|
||||||
|
('0', Term { frame: FrameId(46) }),
|
||||||
|
('1', Term { frame: FrameId(47) }),
|
||||||
|
]
|
||||||
|
.into(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
t.close_tab("bb1").unwrap(),
|
||||||
|
Vertical {
|
||||||
|
top: Horizontal {
|
||||||
|
left: Horizontal {
|
||||||
|
left: Term { frame: FrameId(42) }.into(),
|
||||||
|
right: Term { frame: FrameId(43) }.into(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
right: Term { frame: FrameId(44) }.into(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
bottom: Vertical {
|
||||||
|
top: Term { frame: FrameId(45) }.into(),
|
||||||
|
bottom: Tabs {
|
||||||
|
tabs: vec![('0', Term { frame: FrameId(46) }),].into()
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user