Skip to content

Commit a618d49

Browse files
committed
Initial work on OOBE
1 parent 0020aae commit a618d49

File tree

11 files changed

+618
-33
lines changed

11 files changed

+618
-33
lines changed

Cargo.lock

Lines changed: 59 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

assets/brand/reticle.svg

Lines changed: 15 additions & 0 deletions
Loading

assets/brand/scope-login-bg.png

609 KB
Loading

src/ui/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ random-string = "1.1.0"
2828
rust-embed = "8.5.0"
2929
chrono.workspace = true
3030
catty = "0.1.5"
31+
directories = "5.0.1"
32+
clap = { version = "4.5.21", features = ["derive"] }
3133

3234
[features]
3335
default = ["gpui/x11"]

src/ui/src/app.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,30 @@ use components::theme::ActiveTheme;
22
use gpui::{div, img, rgb, Context, Model, ParentElement, Render, Styled, View, ViewContext, VisualContext};
33
use scope_backend_discord::{channel::DiscordChannel, client::DiscordClient, snowflake::Snowflake};
44

5-
use crate::channel::ChannelView;
5+
use crate::{app_state::StateModel, channel::ChannelView};
66

77
pub struct App {
88
channel: Model<Option<View<ChannelView<DiscordChannel>>>>,
99
}
1010

1111
impl App {
1212
pub fn new(ctx: &mut ViewContext<'_, Self>) -> App {
13-
let token = dotenv::var("DISCORD_TOKEN").expect("Must provide DISCORD_TOKEN in .env");
14-
let demo_channel_id = dotenv::var("DEMO_CHANNEL_ID").expect("Must provide DEMO_CHANNEL_ID in .env");
13+
let demo_channel_id = dotenv::var("DEMO_CHANNEL_ID").unwrap_or("1306357873437179944".to_owned());
1514

1615
let mut context = ctx.to_async();
1716

1817
let channel = ctx.new_model(|_| None);
1918

2019
let async_channel = channel.clone();
2120

21+
let mut async_ctx = ctx.to_async();
22+
2223
ctx
2324
.foreground_executor()
2425
.spawn(async move {
25-
let client = DiscordClient::new(token).await;
26+
let token = async_ctx.update_global::<StateModel, Option<String>>(|global, cx| global.take_token(&mut *cx)).unwrap();
27+
28+
let client = DiscordClient::new(token.expect("Token to be set")).await;
2629

2730
let channel = DiscordChannel::new(
2831
client.clone(),

src/ui/src/app_state.rs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,43 @@
1-
use std::sync::Weak;
1+
use gpui::*;
22

3-
use gpui::{AppContext, Global};
3+
#[derive(Clone)]
4+
pub struct State {
5+
pub token: Option<String>,
6+
}
47

5-
pub struct AppState {}
8+
#[derive(Clone)]
9+
pub struct StateModel {
10+
pub inner: Model<State>,
11+
}
612

7-
struct GlobalAppState();
13+
impl StateModel {
14+
pub fn init(cx: &mut AppContext) {
15+
let model = cx.new_model(|_cx| State { token: None });
16+
let this = Self { inner: model };
17+
cx.set_global(this.clone());
18+
}
819

9-
impl Global for GlobalAppState {}
20+
pub fn update(f: impl FnOnce(&mut Self, &mut AppContext), cx: &mut AppContext) {
21+
if !cx.has_global::<Self>() {
22+
return;
23+
}
24+
cx.update_global::<Self, _>(|mut this, cx| {
25+
f(&mut this, cx);
26+
});
27+
}
1028

11-
impl AppState {
12-
pub fn set_global(_app_state: Weak<AppState>, cx: &mut AppContext) {
13-
cx.set_global(GlobalAppState());
29+
pub fn provide_token(&self, token: String, cx: &mut AppContext) {
30+
self.inner.update(cx, |model, _| model.token = Some(token))
31+
}
32+
33+
pub fn take_token(&self, cx: &mut AppContext) -> Option<String> {
34+
self.inner.update(cx, |model, _| model.token.take())
1435
}
1536
}
37+
38+
impl Global for StateModel {}
39+
40+
#[derive(Clone, Debug)]
41+
pub struct ListChangedEvent {}
42+
43+
impl EventEmitter<ListChangedEvent> for State {}

src/ui/src/main.rs

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ pub mod app;
33
pub mod app_state;
44
pub mod channel;
55
pub mod menu;
6+
pub mod oobe;
67

78
use std::sync::Arc;
89

9-
use app_state::AppState;
10+
use app_state::StateModel;
11+
use clap::Parser;
1012
use components::theme::{hsl, Theme, ThemeColor, ThemeMode};
1113
use gpui::*;
1214
use http_client::anyhow;
1315
use menu::app_menus;
16+
use oobe::login::OobeTokenLogin;
1417

1518
#[derive(rust_embed::RustEmbed)]
1619
#[folder = "../../assets"]
@@ -26,7 +29,8 @@ impl AssetSource for Assets {
2629
}
2730
}
2831

29-
fn init(_: Arc<AppState>, cx: &mut AppContext) -> Result<()> {
32+
fn init(cx: &mut AppContext) -> Result<()> {
33+
StateModel::init(cx);
3034
components::init(cx);
3135

3236
if cfg!(target_os = "macos") {
@@ -44,16 +48,20 @@ fn init(_: Arc<AppState>, cx: &mut AppContext) -> Result<()> {
4448
Ok(())
4549
}
4650

51+
#[derive(Parser, Debug)]
52+
#[command(version, about, long_about = None)]
53+
struct Args {
54+
/// Forces the out of box experience rather than using the token from ENV or appdata
55+
#[arg(long)]
56+
force_oobe: bool,
57+
}
58+
4759
#[tokio::main]
4860
async fn main() {
4961
env_logger::init();
5062

51-
let app_state = Arc::new(AppState {});
52-
5363
App::new().with_assets(Assets).with_http_client(Arc::new(reqwest_client::ReqwestClient::new())).run(move |cx: &mut AppContext| {
54-
AppState::set_global(Arc::downgrade(&app_state), cx);
55-
56-
if let Err(e) = init(app_state.clone(), cx) {
64+
if let Err(e) = init(cx) {
5765
log::error!("{}", e);
5866
return;
5967
}
@@ -67,17 +75,29 @@ async fn main() {
6775
cx.set_global(theme);
6876
cx.refresh();
6977

70-
let opts = WindowOptions {
71-
window_decorations: Some(WindowDecorations::Client),
72-
window_min_size: Some(size(Pixels(800.0), Pixels(600.0))),
73-
titlebar: Some(TitlebarOptions {
74-
appears_transparent: true,
75-
title: Some(SharedString::new_static("scope")),
76-
..Default::default()
77-
}),
78-
..Default::default()
79-
};
80-
81-
cx.open_window(opts, |cx| cx.new_view(crate::app::App::new)).unwrap();
78+
let mut async_cx = cx.to_async();
79+
80+
let args = Args::parse();
81+
82+
cx.foreground_executor()
83+
.spawn(async move {
84+
if let Some(token) = OobeTokenLogin::get_token(&mut async_cx, args.force_oobe).await {
85+
async_cx.update_global(|global: &mut StateModel, cx| global.provide_token(token, cx)).unwrap();
86+
87+
let opts = WindowOptions {
88+
window_decorations: Some(WindowDecorations::Client),
89+
window_min_size: Some(size(Pixels(800.0), Pixels(600.0))),
90+
titlebar: Some(TitlebarOptions {
91+
appears_transparent: true,
92+
title: Some(SharedString::new_static("scope")),
93+
..Default::default()
94+
}),
95+
..Default::default()
96+
};
97+
98+
async_cx.open_window(opts, |cx| cx.new_view(crate::app::App::new)).unwrap();
99+
}
100+
})
101+
.detach();
82102
});
83103
}

src/ui/src/oobe/input.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use components::input::TextInput;
2+
use gpui::{div, rgb, Context, Model, ParentElement, Pixels, Render, Styled, View, VisualContext};
3+
4+
pub struct OobeInput {
5+
title: String,
6+
pub input: View<TextInput>,
7+
}
8+
9+
impl OobeInput {
10+
pub fn create(ctx: &mut gpui::ViewContext<'_, Self>, title: String, secure: bool) -> Self {
11+
let input = ctx.new_view(|cx| {
12+
let mut input = TextInput::new(cx);
13+
14+
if secure {
15+
input.set_masked(true, cx);
16+
}
17+
18+
input
19+
});
20+
21+
OobeInput { title, input }
22+
}
23+
}
24+
25+
impl Render for OobeInput {
26+
fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
27+
div().flex().flex_col().gap(Pixels(4.)).text_color(rgb(0xA7ACBB)).child(self.title.clone()).child(self.input.clone())
28+
}
29+
}

0 commit comments

Comments
 (0)