feat: impl setup restoration

This commit is contained in:
2026-04-26 00:01:57 +01:00
parent a54cc84660
commit 8b28f3d67f
11 changed files with 397 additions and 130 deletions

View File

@@ -2,8 +2,8 @@ use std::time::Duration;
use futures_lite::StreamExt;
use gpui::{
BorrowAppContext, InteractiveElement, ParentElement, StatefulInteractiveElement, Styled, Timer,
div, img, prelude::FluentBuilder,
BorrowAppContext, InteractiveElement, ParentElement, Styled, Timer, div, img,
prelude::FluentBuilder,
};
use rand::RngExt;
@@ -14,27 +14,32 @@ use crate::{
font_icon::{FontIcon, font_icon},
text::text,
},
query::{self, QueryStatus, fetch_query, read_query, use_lazy_query, use_query},
query::{self, QueryStatus, fetch_query, read_query, use_query},
storage,
util::timeout::set_timeout,
};
pub(crate) struct GithubStepView {
is_opening_link: bool,
has_opened_link: bool,
placeholder_code: String,
has_copied_code: bool,
create_device_code_query: query::Entity<api::auth::CreateDeviceCode>,
request_access_token_query: Option<query::Entity<api::auth::RequestAccessToken>>,
user_query: Option<query::Entity<api::user::Fetch>>,
on_success: Option<Box<dyn Fn(&mut gpui::App) + 'static>>,
on_success: Option<Box<dyn Fn(api::user::Id, &mut gpui::App) + 'static>>,
}
pub(crate) fn new(cx: &mut gpui::Context<GithubStepView>) -> GithubStepView {
let mut view = GithubStepView {
is_opening_link: false,
has_opened_link: false,
placeholder_code: "ABCDEFGH".to_owned(),
has_copied_code: false,
create_device_code_query: use_lazy_query(api::auth::CreateDeviceCode, cx),
create_device_code_query: use_query(api::auth::CreateDeviceCode, cx),
request_access_token_query: None,
user_query: None,
@@ -47,7 +52,10 @@ pub(crate) fn new(cx: &mut gpui::Context<GithubStepView>) -> GithubStepView {
impl GithubStepView {
const CHAR_POOL: &'static str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
pub(crate) fn on_success(&mut self, f: impl Fn(&mut gpui::App) + 'static) -> &mut Self {
pub(crate) fn on_success(
&mut self,
f: impl Fn(api::user::Id, &mut gpui::App) + 'static,
) -> &mut Self {
self.on_success = Some(Box::new(f));
self
}
@@ -64,9 +72,25 @@ impl GithubStepView {
};
if let Some(ref code) = code
&& !this.has_opened_link
&& !this.is_opening_link
{
this.has_opened_link = true;
this.begin_auth_flow(code, cx);
this.is_opening_link = true;
this.copy_device_code(code, cx);
let code = code.clone();
set_timeout(
move |weak, cx| {
_ = weak.update(cx, |this, cx| {
this.has_opened_link = true;
this.is_opening_link = false;
this.begin_auth_flow(&code, cx);
cx.notify();
});
},
Duration::from_secs(2),
cx,
);
cx.notify();
}
})
@@ -113,14 +137,22 @@ impl GithubStepView {
.collect()
}
fn copy_device_code(&self, cx: &gpui::App) {
let query = read_query(&self.create_device_code_query, cx);
match query {
QueryStatus::Loaded(data) => {
cx.write_to_clipboard(gpui::ClipboardItem::new_string(data.user_code.clone()));
}
_ => {}
}
fn copy_device_code(&mut self, code: &str, cx: &mut gpui::Context<Self>) {
cx.write_to_clipboard(gpui::ClipboardItem::new_string(code.to_owned()));
self.has_copied_code = true;
set_timeout(
|weak, cx| {
_ = weak.update(cx, |this, cx| {
this.has_copied_code = false;
cx.notify();
});
},
Duration::from_secs(3),
cx,
);
cx.notify();
}
fn begin_auth_flow(&mut self, device_code: &str, cx: &mut gpui::Context<Self>) {
@@ -185,20 +217,11 @@ impl GithubStepView {
})
.unwrap_or_default();
let r = if let Some(task) = fut {
_ = if let Some(task) = fut {
task.await
} else {
Err(anyhow::Error::msg(""))
};
let _ = weak.update(cx, |this, cx| match r {
Ok(_) => {
if let Some(f) = this.on_success.as_ref() {
f(cx);
}
}
Err(e) => {}
});
})
.detach();
}
@@ -223,6 +246,16 @@ impl GithubStepView {
}
}
fn on_next_clicked(&mut self, cx: &mut gpui::Context<Self>) {
let (Some(f), Some(query)) = (&self.on_success, &self.user_query) else {
return;
};
let QueryStatus::Loaded(user) = read_query(query, cx) else {
return;
};
f(user.id, cx);
}
fn device_code_area(&self, cx: &mut gpui::Context<Self>) -> gpui::Div {
let create_device_code_query = read_query(&self.create_device_code_query, cx);
let is_loading_code = matches!(create_device_code_query, QueryStatus::Loading);
@@ -276,13 +309,35 @@ impl GithubStepView {
.child(
text(if is_loading_code {
"Loading..."
} else if self.is_opening_link {
"Copied to clipboard! Opening the browser…"
} else if self.has_copied_code {
"Copied to clipboard!"
} else {
"Click to copy"
})
.text_sm()
.opacity(0.5),
.when(!self.is_opening_link && !self.has_copied_code, |it| {
it.opacity(0.5)
}),
)
}
fn header(&self) -> gpui::Div {
div()
.flex()
.flex_col()
.items_center()
.gap_1p5()
.child(text("Connect to GitHub").text_xl().bold())
.child(text(
if self.has_copied_code {
"You will be redirected to GitHub to authorize access.\nPaste the device code below into GitHub."
} else {
"You will be redirected to GitHub to authorize access.\nCopy the device code below into GitHub."
}
).leading_tight().centered().opacity(0.8))
}
}
impl gpui::Render for GithubStepView {
@@ -291,17 +346,15 @@ impl gpui::Render for GithubStepView {
_window: &mut gpui::Window,
cx: &mut gpui::Context<Self>,
) -> impl gpui::IntoElement {
let create_device_code_query = read_query(&self.create_device_code_query, cx);
let is_loading_code = matches!(create_device_code_query, QueryStatus::Loading);
let (header, body) = match self.user_query {
None => (header(), self.device_code_area(cx)),
let (can_go_next, header, body) = match self.user_query {
None => (false, self.header(), self.device_code_area(cx)),
Some(ref q) => {
let user_query = read_query(q, cx);
match user_query {
QueryStatus::Loaded(user) => (connected_header(), connected_body(user, cx)),
_ => (header(), self.device_code_area(cx)),
QueryStatus::Loaded(user) => {
(true, connected_header(), connected_body(user, cx))
}
_ => (false, self.header(), self.device_code_area(cx)),
}
}
};
@@ -317,25 +370,20 @@ impl gpui::Render for GithubStepView {
.child(
div().flex().flex_row().justify_end().w_full().pb_4().child(
button("connect-to-github-next")
.label("Next")
.when(is_loading_code, |it| it.disabled()),
.label(if can_go_next {
"Next"
} else {
"Waiting for authentication"
})
.on_click(cx.listener(|this, _, _, cx| {
this.on_next_clicked(cx);
}))
.when(!can_go_next, |it| it.disabled()),
),
)
}
}
fn header() -> gpui::Div {
div()
.flex()
.flex_col()
.items_center()
.gap_1p5()
.child(text("Connect to GitHub").text_xl().bold())
.child(text(
"You will be redirected to GitHub to authorize access.\nCopy the device code below into GitHub.",
).leading_tight().centered().opacity(0.8))
}
fn connected_header() -> gpui::Div {
div()
.flex()