Add space order controls
This commit is contained in:
@@ -6,7 +6,7 @@ use gpui::{
|
|||||||
px, rgb,
|
px, rgb,
|
||||||
};
|
};
|
||||||
use gpui_component::{
|
use gpui_component::{
|
||||||
IconName, Sizable, StyledExt,
|
Disableable, IconName, Sizable, StyledExt,
|
||||||
button::{Button, ButtonVariants},
|
button::{Button, ButtonVariants},
|
||||||
scroll::ScrollableElement,
|
scroll::ScrollableElement,
|
||||||
};
|
};
|
||||||
@@ -132,13 +132,21 @@ fn render_spaces_list(snapshot: &BrowserSnapshot, cx: &mut Context<ElyShell>) ->
|
|||||||
.border_t_1()
|
.border_t_1()
|
||||||
.border_color(rgb(colors::HAIRLINE))
|
.border_color(rgb(colors::HAIRLINE))
|
||||||
.children(snapshot.spaces.iter().enumerate().map(|(index, space)| {
|
.children(snapshot.spaces.iter().enumerate().map(|(index, space)| {
|
||||||
render_space_row(index, space, snapshot, space.id() == &snapshot.active_space_id, cx)
|
render_space_row(
|
||||||
|
index,
|
||||||
|
snapshot.spaces.len(),
|
||||||
|
space,
|
||||||
|
snapshot,
|
||||||
|
space.id() == &snapshot.active_space_id,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
}))
|
}))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_space_row(
|
fn render_space_row(
|
||||||
index: usize,
|
index: usize,
|
||||||
|
space_count: usize,
|
||||||
space: &Space,
|
space: &Space,
|
||||||
snapshot: &BrowserSnapshot,
|
snapshot: &BrowserSnapshot,
|
||||||
active: bool,
|
active: bool,
|
||||||
@@ -179,11 +187,72 @@ fn render_space_row(
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(render_space_action(index, space_id, active, cx))
|
.child(render_space_actions(index, space_count, space_id, active, cx))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_space_action(
|
fn render_space_actions(
|
||||||
|
index: usize,
|
||||||
|
space_count: usize,
|
||||||
|
space_id: SpaceId,
|
||||||
|
active: bool,
|
||||||
|
cx: &mut Context<ElyShell>,
|
||||||
|
) -> AnyElement {
|
||||||
|
let can_move_up = index > 0;
|
||||||
|
let can_move_down = index + 1 < space_count;
|
||||||
|
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_2()
|
||||||
|
.child(render_space_order_button(
|
||||||
|
("move-space-up", index),
|
||||||
|
space_id.clone(),
|
||||||
|
IconName::ArrowUp,
|
||||||
|
"Move Space Up",
|
||||||
|
can_move_up,
|
||||||
|
true,
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
.child(render_space_order_button(
|
||||||
|
("move-space-down", index),
|
||||||
|
space_id.clone(),
|
||||||
|
IconName::ArrowDown,
|
||||||
|
"Move Space Down",
|
||||||
|
can_move_down,
|
||||||
|
false,
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
.child(render_space_switch_action(index, space_id, active, cx))
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_space_order_button(
|
||||||
|
id: (&'static str, usize),
|
||||||
|
space_id: SpaceId,
|
||||||
|
icon: IconName,
|
||||||
|
tooltip: &'static str,
|
||||||
|
enabled: bool,
|
||||||
|
moves_up: bool,
|
||||||
|
cx: &mut Context<ElyShell>,
|
||||||
|
) -> AnyElement {
|
||||||
|
Button::new(id)
|
||||||
|
.small()
|
||||||
|
.ghost()
|
||||||
|
.icon(icon)
|
||||||
|
.tooltip(tooltip)
|
||||||
|
.disabled(!enabled)
|
||||||
|
.on_click(cx.listener(move |shell, _, _, cx| {
|
||||||
|
if moves_up {
|
||||||
|
shell.move_space_up(&space_id, cx);
|
||||||
|
} else {
|
||||||
|
shell.move_space_down(&space_id, cx);
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_space_switch_action(
|
||||||
index: usize,
|
index: usize,
|
||||||
space_id: SpaceId,
|
space_id: SpaceId,
|
||||||
active: bool,
|
active: bool,
|
||||||
|
|||||||
@@ -1,9 +1,26 @@
|
|||||||
|
use ely_domain::SpaceId;
|
||||||
use gpui::{Context, Window};
|
use gpui::{Context, Window};
|
||||||
|
|
||||||
use super::{ElyShell, ShellState};
|
use super::{ElyShell, ShellState};
|
||||||
use crate::{SelectNextSpace, SelectPreviousSpace};
|
use crate::{SelectNextSpace, SelectPreviousSpace};
|
||||||
|
|
||||||
impl ElyShell {
|
impl ElyShell {
|
||||||
|
pub(super) fn move_space_up(&mut self, space_id: &SpaceId, cx: &mut Context<Self>) {
|
||||||
|
if let ShellState::Ready(core) = &mut self.state
|
||||||
|
&& core.move_space_up(space_id).is_ok_and(|moved| moved)
|
||||||
|
{
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn move_space_down(&mut self, space_id: &SpaceId, cx: &mut Context<Self>) {
|
||||||
|
if let ShellState::Ready(core) = &mut self.state
|
||||||
|
&& core.move_space_down(space_id).is_ok_and(|moved| moved)
|
||||||
|
{
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn on_select_next_space(
|
pub(super) fn on_select_next_space(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &SelectNextSpace,
|
_: &SelectNextSpace,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ mod plugins;
|
|||||||
mod profiles;
|
mod profiles;
|
||||||
mod reading_list;
|
mod reading_list;
|
||||||
mod site_permissions;
|
mod site_permissions;
|
||||||
|
mod spaces;
|
||||||
mod splits;
|
mod splits;
|
||||||
mod sync;
|
mod sync;
|
||||||
mod tab_groups;
|
mod tab_groups;
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
use ely_domain::SpaceId;
|
||||||
|
|
||||||
|
use super::BrowserCore;
|
||||||
|
use crate::CoreError;
|
||||||
|
|
||||||
|
impl BrowserCore {
|
||||||
|
pub fn move_space_up(&mut self, space_id: &SpaceId) -> Result<bool, CoreError> {
|
||||||
|
let mut ordered_ids = self.sorted_space_ids();
|
||||||
|
let Some(index) = ordered_ids.iter().position(|id| id == space_id) else {
|
||||||
|
return Err(CoreError::SpaceNotFound { id: space_id.clone() });
|
||||||
|
};
|
||||||
|
|
||||||
|
if index == 0 {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ordered_ids.swap(index, index - 1);
|
||||||
|
self.apply_space_order(&ordered_ids)?;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_space_down(&mut self, space_id: &SpaceId) -> Result<bool, CoreError> {
|
||||||
|
let mut ordered_ids = self.sorted_space_ids();
|
||||||
|
let Some(index) = ordered_ids.iter().position(|id| id == space_id) else {
|
||||||
|
return Err(CoreError::SpaceNotFound { id: space_id.clone() });
|
||||||
|
};
|
||||||
|
|
||||||
|
if index + 1 == ordered_ids.len() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ordered_ids.swap(index, index + 1);
|
||||||
|
self.apply_space_order(&ordered_ids)?;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sorted_space_ids(&self) -> Vec<SpaceId> {
|
||||||
|
self.sorted_spaces().iter().map(|space| space.id().clone()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_space_order(&mut self, ordered_ids: &[SpaceId]) -> Result<(), CoreError> {
|
||||||
|
for (sort_key, space_id) in ordered_ids.iter().enumerate() {
|
||||||
|
let Some(space) = self.spaces.iter_mut().find(|space| space.id() == space_id) else {
|
||||||
|
return Err(CoreError::SpaceNotFound { id: space_id.clone() });
|
||||||
|
};
|
||||||
|
|
||||||
|
let sort_key = sort_key as u64;
|
||||||
|
if space.sort_key() != sort_key {
|
||||||
|
space.set_sort_key(sort_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
use ely_browser_core::{BrowserCore, CoreError, InitialBrowserConfig};
|
use ely_browser_core::{BrowserCore, CoreError, InitialBrowserConfig};
|
||||||
use ely_domain::{ArchivePolicy, DEFAULT_SIDEBAR_WIDTH_PX, ProfileId, ProfileKind};
|
use ely_domain::{ArchivePolicy, DEFAULT_SIDEBAR_WIDTH_PX, ProfileId, ProfileKind, SpaceId};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn created_space_binds_current_profile_as_default() -> Result<(), Box<dyn Error>> {
|
fn created_space_binds_current_profile_as_default() -> Result<(), Box<dyn Error>> {
|
||||||
@@ -87,6 +87,49 @@ fn snapshot_orders_spaces_by_sort_key() -> Result<(), Box<dyn Error>> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn moving_spaces_updates_visible_order() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
|
let work_space_id = core.snapshot()?.active_space_id;
|
||||||
|
let research_space_id = core.create_space("Research", "R", 0x9fc9a2)?;
|
||||||
|
let personal_space_id = core.create_space("Personal", "P", 0x8eb7d4)?;
|
||||||
|
|
||||||
|
core.set_space_sort_key(&work_space_id, 20)?;
|
||||||
|
core.set_space_sort_key(&research_space_id, 10)?;
|
||||||
|
core.set_space_sort_key(&personal_space_id, 30)?;
|
||||||
|
|
||||||
|
assert!(core.move_space_up(&work_space_id)?);
|
||||||
|
assert_eq!(
|
||||||
|
ordered_space_ids(&core)?,
|
||||||
|
vec![work_space_id.clone(), research_space_id.clone(), personal_space_id.clone()]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(core.move_space_down(&work_space_id)?);
|
||||||
|
assert_eq!(
|
||||||
|
ordered_space_ids(&core)?,
|
||||||
|
vec![research_space_id, work_space_id, personal_space_id]
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn moving_boundary_or_missing_space_is_safe() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
|
let work_space_id = core.snapshot()?.active_space_id;
|
||||||
|
|
||||||
|
assert!(!core.move_space_up(&work_space_id)?);
|
||||||
|
assert!(!core.move_space_down(&work_space_id)?);
|
||||||
|
|
||||||
|
let missing_space_id = SpaceId::new();
|
||||||
|
let error = match core.move_space_up(&missing_space_id) {
|
||||||
|
Err(error) => error,
|
||||||
|
Ok(_) => return Err("moving a missing space should fail".into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(error, CoreError::SpaceNotFound { id: missing_space_id });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn space_default_profile_updates_with_profile_validation() -> Result<(), Box<dyn Error>> {
|
fn space_default_profile_updates_with_profile_validation() -> Result<(), Box<dyn Error>> {
|
||||||
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
@@ -186,3 +229,7 @@ fn active_space_updated_at(
|
|||||||
|
|
||||||
Ok(space.updated_at())
|
Ok(space.updated_at())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ordered_space_ids(core: &BrowserCore) -> Result<Vec<SpaceId>, Box<dyn Error>> {
|
||||||
|
Ok(core.snapshot()?.spaces.iter().map(|space| space.id().clone()).collect())
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user