logiguard fork: GPUI xdg-activation keyboard-focus serial fix
Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled
Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled
Single-commit orphan branch: full zed-industries/zed @ 8c74db0 source tree with a 3-file patch applied (no upstream history). Patch (crates/gpui_linux/src/linux/wayland/): - serial.rs: add SerialKind::KeyboardEnter - client.rs: store wl_keyboard.enter serial; add latest_serial_of() - window.rs: activate() uses keyboard-enter serial (Mutter focus gate) Mutter honors window activation only when the token carries the keyboard- focus serial from wl_keyboard.enter; GPUI used a stale mouse-press serial. See docs/tray-window-focus-wayland.md in logiguard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
716
crates/onboarding/src/onboarding.rs
Normal file
716
crates/onboarding/src/onboarding.rs
Normal file
@@ -0,0 +1,716 @@
|
||||
use crate::multibuffer_hint::MultibufferHint;
|
||||
use client::{Client, UserStore, zed_urls};
|
||||
use cloud_api_types::Plan;
|
||||
use db::kvp::KeyValueStore;
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
Action, AnyElement, App, AppContext, AsyncWindowContext, Context, Entity, EventEmitter,
|
||||
FocusHandle, Focusable, Global, IntoElement, KeyContext, Render, ScrollHandle, SharedString,
|
||||
Subscription, Task, WeakEntity, Window, actions,
|
||||
};
|
||||
use notifications::status_toast::StatusToast;
|
||||
use project::agent_server_store::AllAgentServersSettings;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use settings::{SettingsStore, VsCodeSettingsSource};
|
||||
use std::sync::Arc;
|
||||
use ui::{
|
||||
Divider, KeyBinding, ParentElement as _, StatefulInteractiveElement, Vector, VectorName,
|
||||
WithScrollbar as _, prelude::*, rems_from_px,
|
||||
};
|
||||
|
||||
pub use workspace::welcome::ShowWelcome;
|
||||
use workspace::welcome::WelcomePage;
|
||||
use workspace::{
|
||||
AppState, Workspace, WorkspaceId,
|
||||
dock::DockPosition,
|
||||
item::{Item, ItemEvent},
|
||||
notifications::NotifyResultExt as _,
|
||||
open_new, register_serializable_item, with_active_or_new_workspace,
|
||||
};
|
||||
use zed_actions::OpenOnboarding;
|
||||
|
||||
mod base_keymap_picker;
|
||||
mod basics_page;
|
||||
pub mod multibuffer_hint;
|
||||
mod theme_preview;
|
||||
|
||||
/// Imports settings from Visual Studio Code.
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)]
|
||||
#[action(namespace = zed)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ImportVsCodeSettings {
|
||||
#[serde(default)]
|
||||
pub skip_prompt: bool,
|
||||
}
|
||||
|
||||
/// Imports settings from Cursor editor.
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)]
|
||||
#[action(namespace = zed)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ImportCursorSettings {
|
||||
#[serde(default)]
|
||||
pub skip_prompt: bool,
|
||||
}
|
||||
|
||||
pub const FIRST_OPEN: &str = "first_open";
|
||||
pub const DOCS_URL: &str = "https://zed.dev/docs/";
|
||||
|
||||
actions!(
|
||||
onboarding,
|
||||
[
|
||||
/// Finish the onboarding process.
|
||||
Finish,
|
||||
/// Sign in while in the onboarding flow.
|
||||
SignIn,
|
||||
/// Open the user account in zed.dev while in the onboarding flow.
|
||||
OpenAccount,
|
||||
/// Resets the welcome screen hints to their initial state.
|
||||
ResetHints
|
||||
]
|
||||
);
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.observe_new(|workspace: &mut Workspace, _, _cx| {
|
||||
workspace
|
||||
.register_action(|_workspace, _: &ResetHints, _, cx| MultibufferHint::set_count(0, cx));
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.on_action(|_: &OpenOnboarding, cx| {
|
||||
with_active_or_new_workspace(cx, |workspace, window, cx| {
|
||||
workspace
|
||||
.with_local_workspace(window, cx, |workspace, window, cx| {
|
||||
let existing = workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<Onboarding>());
|
||||
|
||||
if let Some(existing) = existing {
|
||||
workspace.activate_item(&existing, true, true, window, cx);
|
||||
} else {
|
||||
let settings_page = Onboarding::new(workspace, cx);
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(settings_page),
|
||||
None,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
});
|
||||
|
||||
cx.on_action(|_: &ShowWelcome, cx| {
|
||||
with_active_or_new_workspace(cx, |workspace, window, cx| {
|
||||
workspace
|
||||
.with_local_workspace(window, cx, |workspace, window, cx| {
|
||||
let existing = workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<WelcomePage>());
|
||||
|
||||
if let Some(existing) = existing {
|
||||
workspace.activate_item(&existing, true, true, window, cx);
|
||||
} else {
|
||||
let settings_page = cx
|
||||
.new(|cx| WelcomePage::new(workspace.weak_handle(), false, window, cx));
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(settings_page),
|
||||
None,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
});
|
||||
|
||||
cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
|
||||
workspace.register_action(|_workspace, action: &ImportVsCodeSettings, window, cx| {
|
||||
let fs = <dyn Fs>::global(cx);
|
||||
let action = *action;
|
||||
|
||||
let workspace = cx.weak_entity();
|
||||
|
||||
window
|
||||
.spawn(cx, async move |cx: &mut AsyncWindowContext| {
|
||||
handle_import_vscode_settings(
|
||||
workspace,
|
||||
VsCodeSettingsSource::VsCode,
|
||||
action.skip_prompt,
|
||||
fs,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
|
||||
workspace.register_action(|_workspace, action: &ImportCursorSettings, window, cx| {
|
||||
let fs = <dyn Fs>::global(cx);
|
||||
let action = *action;
|
||||
|
||||
let workspace = cx.weak_entity();
|
||||
|
||||
window
|
||||
.spawn(cx, async move |cx: &mut AsyncWindowContext| {
|
||||
handle_import_vscode_settings(
|
||||
workspace,
|
||||
VsCodeSettingsSource::Cursor,
|
||||
action.skip_prompt,
|
||||
fs,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
|
||||
base_keymap_picker::init(cx);
|
||||
|
||||
register_serializable_item::<Onboarding>(cx);
|
||||
register_serializable_item::<WelcomePage>(cx);
|
||||
}
|
||||
|
||||
pub fn show_onboarding_view(app_state: Arc<AppState>, cx: &mut App) -> Task<anyhow::Result<()>> {
|
||||
telemetry::event!("Onboarding Page Opened");
|
||||
open_new(
|
||||
Default::default(),
|
||||
app_state,
|
||||
cx,
|
||||
|workspace, window, cx| {
|
||||
{
|
||||
workspace.toggle_dock(DockPosition::Left, window, cx);
|
||||
let onboarding_page = Onboarding::new(workspace, cx);
|
||||
workspace.add_item_to_center(Box::new(onboarding_page.clone()), window, cx);
|
||||
|
||||
window.focus(&onboarding_page.focus_handle(cx), cx);
|
||||
|
||||
cx.notify();
|
||||
};
|
||||
let kvp = KeyValueStore::global(cx);
|
||||
db::write_and_log(cx, move || async move {
|
||||
kvp.write_kvp(FIRST_OPEN.to_string(), "false".to_string())
|
||||
.await
|
||||
});
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
struct Onboarding {
|
||||
workspace: WeakEntity<Workspace>,
|
||||
focus_handle: FocusHandle,
|
||||
user_store: Entity<UserStore>,
|
||||
scroll_handle: ScrollHandle,
|
||||
_settings_subscription: Subscription,
|
||||
}
|
||||
|
||||
impl Onboarding {
|
||||
fn new(workspace: &Workspace, cx: &mut App) -> Entity<Self> {
|
||||
let font_family_cache = theme::FontFamilyCache::global(cx);
|
||||
|
||||
let installed_agents = cx
|
||||
.global::<SettingsStore>()
|
||||
.get::<AllAgentServersSettings>(None)
|
||||
.clone();
|
||||
let client = Client::global(cx);
|
||||
let status = *client.status().borrow();
|
||||
let plan = workspace.user_store().read(cx).plan();
|
||||
let zed_agent_state = if status.is_signed_out()
|
||||
|| matches!(
|
||||
status,
|
||||
client::Status::AuthenticationError | client::Status::ConnectionError
|
||||
) {
|
||||
"signed_out"
|
||||
} else if status.is_signing_in() {
|
||||
"signing_in"
|
||||
} else {
|
||||
match plan {
|
||||
Some(Plan::ZedPro) => "pro",
|
||||
Some(Plan::ZedProTrial) => "trial",
|
||||
Some(Plan::ZedBusiness) => "business",
|
||||
Some(Plan::ZedStudent) => "student",
|
||||
Some(Plan::ZedFree) | None => "free",
|
||||
}
|
||||
};
|
||||
let agents_installed = basics_page::FEATURED_AGENT_IDS
|
||||
.iter()
|
||||
.filter(|id| installed_agents.contains_key(**id))
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
telemetry::event!(
|
||||
"Welcome Agent Setup Viewed",
|
||||
zed_agent = zed_agent_state,
|
||||
agents_installed = agents_installed,
|
||||
);
|
||||
|
||||
cx.new(|cx| {
|
||||
cx.spawn(async move |this, cx| {
|
||||
font_family_cache.prefetch(cx).await;
|
||||
this.update(cx, |_, cx| {
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
workspace: workspace.weak_handle(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
user_store: workspace.user_store().clone(),
|
||||
_settings_subscription: cx
|
||||
.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn on_finish(_: &Finish, _: &mut Window, cx: &mut App) {
|
||||
telemetry::event!("Finish Setup");
|
||||
go_to_welcome_page(cx);
|
||||
}
|
||||
|
||||
fn handle_sign_in(&mut self, _: &SignIn, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let client = Client::global(cx);
|
||||
let workspace = self.workspace.clone();
|
||||
|
||||
window
|
||||
.spawn(cx, async move |mut cx| {
|
||||
client
|
||||
.sign_in_with_optional_connect(true, &cx)
|
||||
.await
|
||||
.notify_workspace_async_err(workspace, &mut cx);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn handle_open_account(_: &OpenAccount, _: &mut Window, cx: &mut App) {
|
||||
cx.open_url(&zed_urls::account_url(cx))
|
||||
}
|
||||
|
||||
fn render_page(&mut self, cx: &mut Context<Self>) -> AnyElement {
|
||||
crate::basics_page::render_basics_page(&self.user_store, cx).into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Onboarding {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.image_cache(gpui::retain_all("onboarding-page"))
|
||||
.key_context({
|
||||
let mut ctx = KeyContext::new_with_defaults();
|
||||
ctx.add("Onboarding");
|
||||
ctx.add("menu");
|
||||
ctx
|
||||
})
|
||||
.track_focus(&self.focus_handle)
|
||||
.size_full()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.on_action(Self::on_finish)
|
||||
.on_action(cx.listener(Self::handle_sign_in))
|
||||
.on_action(Self::handle_open_account)
|
||||
.on_action(cx.listener(|_, _: &menu::SelectNext, window, cx| {
|
||||
window.focus_next(cx);
|
||||
cx.notify();
|
||||
}))
|
||||
.on_action(cx.listener(|_, _: &menu::SelectPrevious, window, cx| {
|
||||
window.focus_prev(cx);
|
||||
cx.notify();
|
||||
}))
|
||||
.vertical_scrollbar_for(&self.scroll_handle, window, cx)
|
||||
.child(
|
||||
div()
|
||||
.id("page-content")
|
||||
.size_full()
|
||||
.overflow_y_scroll()
|
||||
.child(
|
||||
v_flex()
|
||||
.min_w_0()
|
||||
.max_w(rems_from_px(780.))
|
||||
.w_full()
|
||||
.mx_auto()
|
||||
.p_12()
|
||||
.gap_6()
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_4()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_4()
|
||||
.child(Vector::square(VectorName::ZedLogo, rems(2.5)))
|
||||
.child(
|
||||
v_flex()
|
||||
.child(
|
||||
Headline::new("Welcome to Zed")
|
||||
.size(HeadlineSize::Small),
|
||||
)
|
||||
.child(
|
||||
Label::new("The editor for what's next")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.italic(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child({
|
||||
Button::new("finish_setup", "Finish Setup")
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::Medium)
|
||||
.width(rems_from_px(200.))
|
||||
.key_binding(KeyBinding::for_action_in(
|
||||
&Finish,
|
||||
&self.focus_handle,
|
||||
cx,
|
||||
))
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(Finish.boxed_clone(), cx);
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(Divider::horizontal().color(ui::DividerColor::BorderVariant))
|
||||
.child(self.render_page(cx)),
|
||||
)
|
||||
.track_scroll(&self.scroll_handle),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<ItemEvent> for Onboarding {}
|
||||
|
||||
impl Focusable for Onboarding {
|
||||
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Item for Onboarding {
|
||||
type Event = ItemEvent;
|
||||
|
||||
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||
"Onboarding".into()
|
||||
}
|
||||
|
||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||
Some("Onboarding Page Opened")
|
||||
}
|
||||
|
||||
fn show_toolbar(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn can_split(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn clone_on_split(
|
||||
&self,
|
||||
_workspace_id: Option<WorkspaceId>,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>> {
|
||||
Task::ready(Some(cx.new(|cx| Onboarding {
|
||||
workspace: self.workspace.clone(),
|
||||
user_store: self.user_store.clone(),
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
_settings_subscription: cx.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
|
||||
})))
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event, f: &mut dyn FnMut(workspace::item::ItemEvent)) {
|
||||
f(*event)
|
||||
}
|
||||
}
|
||||
|
||||
fn go_to_welcome_page(cx: &mut App) {
|
||||
with_active_or_new_workspace(cx, |workspace, window, cx| {
|
||||
let Some((onboarding_id, onboarding_idx)) = workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.enumerate()
|
||||
.find_map(|(idx, item)| {
|
||||
let _ = item.downcast::<Onboarding>()?;
|
||||
Some((item.item_id(), idx))
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
// Get the index here to get around the borrow checker
|
||||
let idx = pane.items().enumerate().find_map(|(idx, item)| {
|
||||
let _ = item.downcast::<WelcomePage>()?;
|
||||
Some(idx)
|
||||
});
|
||||
|
||||
if let Some(idx) = idx {
|
||||
pane.activate_item(idx, true, true, window, cx);
|
||||
} else {
|
||||
let item = Box::new(
|
||||
cx.new(|cx| WelcomePage::new(workspace.weak_handle(), false, window, cx)),
|
||||
);
|
||||
pane.add_item(item, true, true, Some(onboarding_idx), window, cx);
|
||||
}
|
||||
|
||||
pane.remove_item(onboarding_id, false, false, window, cx);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn handle_import_vscode_settings(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
source: VsCodeSettingsSource,
|
||||
skip_prompt: bool,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) {
|
||||
use util::truncate_and_remove_front;
|
||||
|
||||
let vscode_settings =
|
||||
match settings::VsCodeSettings::load_user_settings(source, fs.clone()).await {
|
||||
Ok(vscode_settings) => vscode_settings,
|
||||
Err(err) => {
|
||||
zlog::error!("{err:?}");
|
||||
let _ = cx.prompt(
|
||||
gpui::PromptLevel::Info,
|
||||
&format!("Could not find or load a {source} settings file"),
|
||||
None,
|
||||
&["Ok"],
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if !skip_prompt {
|
||||
let prompt = cx.prompt(
|
||||
gpui::PromptLevel::Warning,
|
||||
&format!(
|
||||
"Importing {} settings may overwrite your existing settings. \
|
||||
Will import settings from {}",
|
||||
vscode_settings.source,
|
||||
truncate_and_remove_front(&vscode_settings.path.to_string_lossy(), 128),
|
||||
),
|
||||
None,
|
||||
&["Ok", "Cancel"],
|
||||
);
|
||||
let result = cx.spawn(async move |_| prompt.await.ok()).await;
|
||||
if result != Some(0) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let Ok(result_channel) = cx.update(|_, cx| {
|
||||
let source = vscode_settings.source;
|
||||
let path = vscode_settings.path.clone();
|
||||
let result_channel = cx
|
||||
.global::<SettingsStore>()
|
||||
.import_vscode_settings(fs, vscode_settings);
|
||||
zlog::info!("Imported {source} settings from {}", path.display());
|
||||
result_channel
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let result = result_channel.await;
|
||||
workspace
|
||||
.update_in(cx, |workspace, _, cx| match result {
|
||||
Ok(_) => {
|
||||
let confirmation_toast = StatusToast::new(
|
||||
format!("Your {} settings were successfully imported.", source),
|
||||
cx,
|
||||
|this, _| {
|
||||
this.icon(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
)
|
||||
.dismiss_button(true)
|
||||
},
|
||||
);
|
||||
SettingsImportState::update(cx, |state, _| match source {
|
||||
VsCodeSettingsSource::VsCode => {
|
||||
state.vscode = true;
|
||||
}
|
||||
VsCodeSettingsSource::Cursor => {
|
||||
state.cursor = true;
|
||||
}
|
||||
});
|
||||
workspace.toggle_status_toast(confirmation_toast, cx);
|
||||
}
|
||||
Err(_) => {
|
||||
let error_toast = StatusToast::new(
|
||||
"Failed to import settings. See log for details",
|
||||
cx,
|
||||
|this, _| {
|
||||
this.icon(
|
||||
Icon::new(IconName::Close)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Error),
|
||||
)
|
||||
.action("Open Log", |window, cx| {
|
||||
window.dispatch_action(workspace::OpenLog.boxed_clone(), cx)
|
||||
})
|
||||
.dismiss_button(true)
|
||||
},
|
||||
);
|
||||
workspace.toggle_status_toast(error_toast, cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone)]
|
||||
pub struct SettingsImportState {
|
||||
pub cursor: bool,
|
||||
pub vscode: bool,
|
||||
}
|
||||
|
||||
impl Global for SettingsImportState {}
|
||||
|
||||
impl SettingsImportState {
|
||||
pub fn global(cx: &App) -> Self {
|
||||
cx.try_global().cloned().unwrap_or_default()
|
||||
}
|
||||
pub fn update<R>(cx: &mut App, f: impl FnOnce(&mut Self, &mut App) -> R) -> R {
|
||||
cx.update_default_global(f)
|
||||
}
|
||||
}
|
||||
|
||||
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::OnboardingPagesDb::global(cx),
|
||||
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>>> {
|
||||
let db = persistence::OnboardingPagesDb::global(cx);
|
||||
window.spawn(cx, async move |cx| {
|
||||
if let Some(_) = db.get_onboarding_page(item_id, workspace_id)? {
|
||||
workspace.update(cx, |workspace, cx| Onboarding::new(workspace, cx))
|
||||
} 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 db = persistence::OnboardingPagesDb::global(cx);
|
||||
Some(
|
||||
cx.background_spawn(
|
||||
async move { db.save_onboarding_page(item_id, workspace_id).await },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn should_serialize(&self, event: &Self::Event) -> bool {
|
||||
event == &ItemEvent::UpdateTab
|
||||
}
|
||||
}
|
||||
|
||||
mod persistence {
|
||||
use db::{
|
||||
query,
|
||||
sqlez::{domain::Domain, thread_safe_connection::ThreadSafeConnection},
|
||||
sqlez_macros::sql,
|
||||
};
|
||||
use workspace::WorkspaceDb;
|
||||
|
||||
pub struct OnboardingPagesDb(ThreadSafeConnection);
|
||||
|
||||
impl Domain for OnboardingPagesDb {
|
||||
const NAME: &str = stringify!(OnboardingPagesDb);
|
||||
|
||||
const MIGRATIONS: &[&str] = &[
|
||||
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;
|
||||
),
|
||||
sql!(
|
||||
CREATE TABLE onboarding_pages_2 (
|
||||
workspace_id INTEGER,
|
||||
item_id INTEGER UNIQUE,
|
||||
|
||||
PRIMARY KEY(workspace_id, item_id),
|
||||
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
|
||||
ON DELETE CASCADE
|
||||
) STRICT;
|
||||
INSERT INTO onboarding_pages_2 SELECT workspace_id, item_id FROM onboarding_pages;
|
||||
DROP TABLE onboarding_pages;
|
||||
ALTER TABLE onboarding_pages_2 RENAME TO onboarding_pages;
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
db::static_connection!(OnboardingPagesDb, [WorkspaceDb]);
|
||||
|
||||
impl OnboardingPagesDb {
|
||||
query! {
|
||||
pub async fn save_onboarding_page(
|
||||
item_id: workspace::ItemId,
|
||||
workspace_id: workspace::WorkspaceId
|
||||
) -> Result<()> {
|
||||
INSERT OR REPLACE INTO onboarding_pages(item_id, workspace_id)
|
||||
VALUES (?, ?)
|
||||
}
|
||||
}
|
||||
|
||||
query! {
|
||||
pub fn get_onboarding_page(
|
||||
item_id: workspace::ItemId,
|
||||
workspace_id: workspace::WorkspaceId
|
||||
) -> Result<Option<workspace::ItemId>> {
|
||||
SELECT item_id
|
||||
FROM onboarding_pages
|
||||
WHERE item_id = ? AND workspace_id = ?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user