onboarding: Serialize onboarding page (#35490)
Closes #ISSUE Serializes the onboarding page to the database to ensure that if Zed is closed during onboarding, re-opening Zed restores the onboarding state and the most recently active page (Basics, Editing, etc) restored. Also has the nice side effect of making dev a bit nicer as it removes the need to re-open onboarding and navigate to the correct page on each build. Release Notes: - N/A *or* Added/Fixed/Improved ...
This commit is contained in:
parent
ac75593198
commit
561ccf86aa
1 changed files with 134 additions and 9 deletions
|
@ -22,7 +22,7 @@ use workspace::{
|
||||||
dock::DockPosition,
|
dock::DockPosition,
|
||||||
item::{Item, ItemEvent},
|
item::{Item, ItemEvent},
|
||||||
notifications::NotifyResultExt as _,
|
notifications::NotifyResultExt as _,
|
||||||
open_new, with_active_or_new_workspace,
|
open_new, register_serializable_item, with_active_or_new_workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod ai_setup_page;
|
mod ai_setup_page;
|
||||||
|
@ -197,6 +197,7 @@ pub fn init(cx: &mut App) {
|
||||||
.detach();
|
.detach();
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
register_serializable_item::<Onboarding>(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_onboarding_view(app_state: Arc<AppState>, cx: &mut App) -> Task<anyhow::Result<()>> {
|
pub fn show_onboarding_view(app_state: Arc<AppState>, cx: &mut App) -> Task<anyhow::Result<()>> {
|
||||||
|
@ -247,6 +248,12 @@ impl Onboarding {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_page(&mut self, page: SelectedPage, cx: &mut Context<Self>) {
|
||||||
|
self.selected_page = page;
|
||||||
|
cx.notify();
|
||||||
|
cx.emit(ItemEvent::UpdateTab);
|
||||||
|
}
|
||||||
|
|
||||||
fn render_nav_buttons(
|
fn render_nav_buttons(
|
||||||
&mut self,
|
&mut self,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
|
@ -306,8 +313,7 @@ impl Onboarding {
|
||||||
IntoElement::into_any_element,
|
IntoElement::into_any_element,
|
||||||
))
|
))
|
||||||
.on_click(cx.listener(move |this, _, _, cx| {
|
.on_click(cx.listener(move |this, _, _, cx| {
|
||||||
this.selected_page = page;
|
this.set_page(page, cx);
|
||||||
cx.notify();
|
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -470,16 +476,13 @@ impl Render for Onboarding {
|
||||||
.size_full()
|
.size_full()
|
||||||
.bg(cx.theme().colors().editor_background)
|
.bg(cx.theme().colors().editor_background)
|
||||||
.on_action(cx.listener(|this, _: &ActivateBasicsPage, _, cx| {
|
.on_action(cx.listener(|this, _: &ActivateBasicsPage, _, cx| {
|
||||||
this.selected_page = SelectedPage::Basics;
|
this.set_page(SelectedPage::Basics, cx);
|
||||||
cx.notify();
|
|
||||||
}))
|
}))
|
||||||
.on_action(cx.listener(|this, _: &ActivateEditingPage, _, cx| {
|
.on_action(cx.listener(|this, _: &ActivateEditingPage, _, cx| {
|
||||||
this.selected_page = SelectedPage::Editing;
|
this.set_page(SelectedPage::Editing, cx);
|
||||||
cx.notify();
|
|
||||||
}))
|
}))
|
||||||
.on_action(cx.listener(|this, _: &ActivateAISetupPage, _, cx| {
|
.on_action(cx.listener(|this, _: &ActivateAISetupPage, _, cx| {
|
||||||
this.selected_page = SelectedPage::AiSetup;
|
this.set_page(SelectedPage::AiSetup, cx);
|
||||||
cx.notify();
|
|
||||||
}))
|
}))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
@ -594,3 +597,125 @@ pub async fn handle_import_vscode_settings(
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl workspace::SerializableItem for Onboarding {
|
||||||
|
fn serialized_item_kind() -> &'static str {
|
||||||
|
"OnboardingPage"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup(
|
||||||
|
workspace_id: workspace::WorkspaceId,
|
||||||
|
alive_items: Vec<workspace::ItemId>,
|
||||||
|
_window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> gpui::Task<gpui::Result<()>> {
|
||||||
|
workspace::delete_unloaded_items(
|
||||||
|
alive_items,
|
||||||
|
workspace_id,
|
||||||
|
"onboarding_pages",
|
||||||
|
&persistence::ONBOARDING_PAGES,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize(
|
||||||
|
_project: Entity<project::Project>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
workspace_id: workspace::WorkspaceId,
|
||||||
|
item_id: workspace::ItemId,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> gpui::Task<gpui::Result<Entity<Self>>> {
|
||||||
|
window.spawn(cx, async move |cx| {
|
||||||
|
if let Some(page_number) =
|
||||||
|
persistence::ONBOARDING_PAGES.get_onboarding_page(item_id, workspace_id)?
|
||||||
|
{
|
||||||
|
let page = match page_number {
|
||||||
|
0 => Some(SelectedPage::Basics),
|
||||||
|
1 => Some(SelectedPage::Editing),
|
||||||
|
2 => Some(SelectedPage::AiSetup),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
let onboarding_page = Onboarding::new(workspace, cx);
|
||||||
|
if let Some(page) = page {
|
||||||
|
zlog::info!("Onboarding page {page:?} loaded");
|
||||||
|
onboarding_page.update(cx, |onboarding_page, cx| {
|
||||||
|
onboarding_page.set_page(page, cx);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onboarding_page
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!("No onboarding page to deserialize"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize(
|
||||||
|
&mut self,
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
item_id: workspace::ItemId,
|
||||||
|
_closing: bool,
|
||||||
|
_window: &mut Window,
|
||||||
|
cx: &mut ui::Context<Self>,
|
||||||
|
) -> Option<gpui::Task<gpui::Result<()>>> {
|
||||||
|
let workspace_id = workspace.database_id()?;
|
||||||
|
let page_number = self.selected_page as u16;
|
||||||
|
Some(cx.background_spawn(async move {
|
||||||
|
persistence::ONBOARDING_PAGES
|
||||||
|
.save_onboarding_page(item_id, workspace_id, page_number)
|
||||||
|
.await
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_serialize(&self, event: &Self::Event) -> bool {
|
||||||
|
event == &ItemEvent::UpdateTab
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod persistence {
|
||||||
|
use db::{define_connection, query, sqlez_macros::sql};
|
||||||
|
use workspace::WorkspaceDb;
|
||||||
|
|
||||||
|
define_connection! {
|
||||||
|
pub static ref ONBOARDING_PAGES: OnboardingPagesDb<WorkspaceDb> =
|
||||||
|
&[
|
||||||
|
sql!(
|
||||||
|
CREATE TABLE onboarding_pages (
|
||||||
|
workspace_id INTEGER,
|
||||||
|
item_id INTEGER UNIQUE,
|
||||||
|
page_number INTEGER,
|
||||||
|
|
||||||
|
PRIMARY KEY(workspace_id, item_id),
|
||||||
|
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
) STRICT;
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OnboardingPagesDb {
|
||||||
|
query! {
|
||||||
|
pub async fn save_onboarding_page(
|
||||||
|
item_id: workspace::ItemId,
|
||||||
|
workspace_id: workspace::WorkspaceId,
|
||||||
|
page_number: u16
|
||||||
|
) -> Result<()> {
|
||||||
|
INSERT OR REPLACE INTO onboarding_pages(item_id, workspace_id, page_number)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query! {
|
||||||
|
pub fn get_onboarding_page(
|
||||||
|
item_id: workspace::ItemId,
|
||||||
|
workspace_id: workspace::WorkspaceId
|
||||||
|
) -> Result<Option<u16>> {
|
||||||
|
SELECT page_number
|
||||||
|
FROM onboarding_pages
|
||||||
|
WHERE item_id = ? AND workspace_id = ?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue