some more bs
This commit is contained in:
15
src/api.rs
15
src/api.rs
@@ -1,9 +1,22 @@
|
|||||||
use crate::query;
|
use crate::query;
|
||||||
|
|
||||||
pub(crate) mod repo;
|
pub(crate) mod repo;
|
||||||
|
pub(crate) mod user;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct QueryContext {
|
pub struct QueryContext {
|
||||||
pub(crate) http: reqwest::Client,
|
pub(crate) http: reqwest::Client,
|
||||||
|
pub(crate) auth: Option<Auth>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl query::Context for QueryContext {}
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct Auth {
|
||||||
|
pub(crate) access_token: String,
|
||||||
|
pub(crate) refresh_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Error {
|
||||||
|
Unauthenticated,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl query::Context for QueryContext {}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ impl query::QueryFn<QueryContext> for List {
|
|||||||
"repo.list"
|
"repo.list"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, c: &QueryContext) -> Result<Self::Data, Self::Error> {
|
async fn run(&self, _c: &QueryContext) -> Result<Self::Data, Self::Error> {
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/api/user.rs
Normal file
18
src/api/user.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
use crate::{api, query};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Fetch;
|
||||||
|
|
||||||
|
impl query::QueryFn<api::QueryContext> for Fetch {
|
||||||
|
type Data = api::Error;
|
||||||
|
|
||||||
|
type Error = api::Error;
|
||||||
|
|
||||||
|
fn key(&self) -> &'static str {
|
||||||
|
"user"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(&self, c: &api::QueryContext) -> Result<Self::Data, Self::Error> {
|
||||||
|
Err(api::Error::Unauthenticated)
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/app.rs
15
src/app.rs
@@ -1,15 +1,14 @@
|
|||||||
use gpui::{div, prelude::*};
|
use gpui::{div, prelude::*};
|
||||||
|
|
||||||
use crate::{api, app};
|
|
||||||
use crate::query;
|
|
||||||
use crate::dashboard;
|
use crate::dashboard;
|
||||||
|
use crate::query;
|
||||||
use crate::theme;
|
use crate::theme;
|
||||||
use crate::titlebar;
|
use crate::titlebar;
|
||||||
|
use crate::{api, app};
|
||||||
|
|
||||||
pub struct Global {
|
pub struct Global {
|
||||||
pub safe_area: gpui::Bounds<gpui::Pixels>,
|
pub safe_area: gpui::Bounds<gpui::Pixels>,
|
||||||
pub current_theme: theme::Theme,
|
pub current_theme: theme::Theme,
|
||||||
pub query_store: query::Store<api::QueryContext>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Chrome {}
|
pub struct Chrome {}
|
||||||
@@ -17,8 +16,9 @@ pub struct Chrome {}
|
|||||||
impl Chrome {
|
impl Chrome {
|
||||||
pub fn new(window: &mut gpui::Window, cx: &mut gpui::Context<Self>) -> Self {
|
pub fn new(window: &mut gpui::Window, cx: &mut gpui::Context<Self>) -> Self {
|
||||||
cx.observe_window_appearance(window, |_, window, cx| {
|
cx.observe_window_appearance(window, |_, window, cx| {
|
||||||
cx.update_global::<app::Global, ()>(|global, _cx| {
|
cx.update_global::<app::Global, ()>(|global, cx| {
|
||||||
global.current_theme = window.appearance().into();
|
global.current_theme = window.appearance().into();
|
||||||
|
cx.notify();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
@@ -33,15 +33,16 @@ impl gpui::Render for Chrome {
|
|||||||
_window: &mut gpui::Window,
|
_window: &mut gpui::Window,
|
||||||
cx: &mut gpui::Context<Self>,
|
cx: &mut gpui::Context<Self>,
|
||||||
) -> impl gpui::IntoElement {
|
) -> impl gpui::IntoElement {
|
||||||
let title_bar = cx.new(|_| titlebar::TitleBar {});
|
let title_bar = cx.new(|cx| titlebar::TitleBar::new(cx));
|
||||||
|
|
||||||
let dashboard = cx.new(|_| dashboard::Screen {
|
let dashboard = cx.new(|_| dashboard::Screen {
|
||||||
text: "World".into(),
|
text: "World".into(),
|
||||||
});
|
});
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.size_full()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
|
.size_full()
|
||||||
.child(title_bar)
|
.child(title_bar)
|
||||||
.child(dashboard)
|
.child(dashboard)
|
||||||
}
|
}
|
||||||
@@ -54,5 +55,5 @@ pub fn current_theme<'a, E>(cx: &'a gpui::Context<E>) -> &'a theme::Theme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn query_store<'a, E>(cx: &'a gpui::Context<E>) -> &'a query::Store<api::QueryContext> {
|
pub fn query_store<'a, E>(cx: &'a gpui::Context<E>) -> &'a query::Store<api::QueryContext> {
|
||||||
&cx.global::<Global>().query_store
|
cx.global::<query::Store<api::QueryContext>>()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
|
pub mod button;
|
||||||
pub mod font_icon;
|
pub mod font_icon;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|||||||
35
src/component/button.rs
Normal file
35
src/component/button.rs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
use gpui::{FontWeight, InteractiveElement, ParentElement, Styled, div};
|
||||||
|
|
||||||
|
use crate::{app, component::text::Text};
|
||||||
|
|
||||||
|
pub struct Button(gpui::Stateful<gpui::Div>);
|
||||||
|
|
||||||
|
pub fn button<T>(id: impl Into<gpui::ElementId>, cx: &gpui::Context<T>) -> Button {
|
||||||
|
let theme = app::current_theme(cx);
|
||||||
|
Button(
|
||||||
|
div()
|
||||||
|
.id(id)
|
||||||
|
.rounded_sm()
|
||||||
|
.bg(theme.colors.accent)
|
||||||
|
.text_xs()
|
||||||
|
.text_color(theme.colors.accent_text)
|
||||||
|
.font_weight(FontWeight(500.))
|
||||||
|
.px_2p5()
|
||||||
|
.py_0p5(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Button {
|
||||||
|
pub fn label(mut self, s: impl Text) -> Self {
|
||||||
|
self.0 = self.0.child(s);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl gpui::IntoElement for Button {
|
||||||
|
type Element = gpui::Stateful<gpui::Div>;
|
||||||
|
|
||||||
|
fn into_element(self) -> Self::Element {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use gpui::{div, prelude::*};
|
use gpui::{div, prelude::*};
|
||||||
|
|
||||||
use crate::theme::Variant;
|
use crate::{app, theme::Variant};
|
||||||
|
|
||||||
pub struct Screen {
|
pub struct Screen {
|
||||||
pub text: gpui::SharedString,
|
pub text: gpui::SharedString,
|
||||||
@@ -10,39 +10,39 @@ impl Render for Screen {
|
|||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
_window: &mut gpui::Window,
|
_window: &mut gpui::Window,
|
||||||
_cx: &mut gpui::Context<Self>,
|
cx: &mut gpui::Context<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let builtin = Variant::VioletDark;
|
let theme = app::current_theme(cx);
|
||||||
let theme = builtin.theme();
|
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_1()
|
||||||
.size_full()
|
.flex_row()
|
||||||
.gap_3()
|
.w_full()
|
||||||
|
.gap_2()
|
||||||
|
.p_2p5()
|
||||||
|
.pt_0()
|
||||||
.bg(theme.colors.background)
|
.bg(theme.colors.background)
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.items_center()
|
.items_center()
|
||||||
.shadow_lg()
|
.shadow_lg()
|
||||||
.text_xl()
|
.text_xl()
|
||||||
.text_color(theme.colors.text)
|
.text_color(theme.colors.text)
|
||||||
.child(format!("Hello, {}!", &self.text))
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_sm()
|
.h_full()
|
||||||
.text_color(theme.colors.text_muted)
|
.flex()
|
||||||
.child(format!("Built-in theme: {}", builtin.label())),
|
.w_1_3()
|
||||||
|
.bg(theme.colors.surface)
|
||||||
|
.rounded_lg(),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
|
.h_full()
|
||||||
.flex()
|
.flex()
|
||||||
.gap_2()
|
.w_2_3()
|
||||||
.child(div().size_8().bg(theme.colors.surface))
|
.bg(theme.colors.surface)
|
||||||
.child(div().size_8().bg(theme.colors.surface_elevated))
|
.rounded_lg(),
|
||||||
.child(div().size_8().bg(theme.colors.accent))
|
|
||||||
.child(div().size_8().bg(theme.colors.success))
|
|
||||||
.child(div().size_8().bg(theme.colors.warning))
|
|
||||||
.child(div().size_8().bg(theme.colors.danger)),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/main.rs
15
src/main.rs
@@ -1,5 +1,6 @@
|
|||||||
use gpui::{bounds, point, prelude::*, px, size};
|
use gpui::{bounds, point, prelude::*, px, size};
|
||||||
|
|
||||||
|
mod api;
|
||||||
mod app;
|
mod app;
|
||||||
mod asset;
|
mod asset;
|
||||||
mod colors;
|
mod colors;
|
||||||
@@ -8,7 +9,6 @@ mod dashboard;
|
|||||||
mod query;
|
mod query;
|
||||||
mod theme;
|
mod theme;
|
||||||
mod titlebar;
|
mod titlebar;
|
||||||
mod api;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
gpui::Application::new()
|
gpui::Application::new()
|
||||||
@@ -18,25 +18,30 @@ fn main() {
|
|||||||
|
|
||||||
fn setup_application(cx: &mut gpui::App) {
|
fn setup_application(cx: &mut gpui::App) {
|
||||||
let window_bounds = gpui::Bounds::centered(None, size(px(800.), px(600.0)), cx);
|
let window_bounds = gpui::Bounds::centered(None, size(px(800.), px(600.0)), cx);
|
||||||
|
let query_store = query::Store::new(
|
||||||
|
api::QueryContext {
|
||||||
|
http: reqwest::Client::new(),
|
||||||
|
auth: None,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
let global = app::Global {
|
let global = app::Global {
|
||||||
safe_area: bounds(point(px(0.), px(0.)), size(px(72.), px(12.))),
|
safe_area: bounds(point(px(0.), px(0.)), size(px(72.), px(12.))),
|
||||||
current_theme: cx.window_appearance().into(),
|
current_theme: cx.window_appearance().into(),
|
||||||
query_store: query::Store::new(api::QueryContext {
|
|
||||||
http: reqwest::Client::new(),
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let top_left = global.safe_area.origin;
|
let top_left = global.safe_area.origin;
|
||||||
|
|
||||||
cx.set_global(global);
|
cx.set_global(global);
|
||||||
|
cx.set_global(query_store);
|
||||||
|
|
||||||
cx.open_window(
|
cx.open_window(
|
||||||
gpui::WindowOptions {
|
gpui::WindowOptions {
|
||||||
window_bounds: Some(gpui::WindowBounds::Windowed(window_bounds)),
|
window_bounds: Some(gpui::WindowBounds::Windowed(window_bounds)),
|
||||||
titlebar: Some(gpui::TitlebarOptions {
|
titlebar: Some(gpui::TitlebarOptions {
|
||||||
appears_transparent: true,
|
appears_transparent: true,
|
||||||
traffic_light_position: Some(top_left + point(px(8.), px(8.))),
|
traffic_light_position: Some(top_left + point(px(12.), px(12.))),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
37
src/query.rs
37
src/query.rs
@@ -1,10 +1,9 @@
|
|||||||
use crate::app;
|
|
||||||
use gpui::{AppContext, BorrowAppContext};
|
use gpui::{AppContext, BorrowAppContext};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
pub trait QueryFn<C>: Clone + 'static
|
pub trait QueryFn<C>: Clone + 'static
|
||||||
where
|
where
|
||||||
C: Context + 'static,
|
C: Context,
|
||||||
{
|
{
|
||||||
type Data: 'static;
|
type Data: 'static;
|
||||||
type Error: 'static;
|
type Error: 'static;
|
||||||
@@ -45,6 +44,27 @@ where
|
|||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn read_query<'a, F, T, C>(
|
||||||
|
query_fn: F,
|
||||||
|
cx: &'a gpui::Context<T>,
|
||||||
|
) -> QueryStatus<'a, F::Data, F::Error>
|
||||||
|
where
|
||||||
|
C: Context + 'static,
|
||||||
|
F: QueryFn<C>,
|
||||||
|
T: 'static,
|
||||||
|
{
|
||||||
|
let store = cx.global::<Store<C>>();
|
||||||
|
let Some(ent) = store.query_data.get(query_fn.key()) else {
|
||||||
|
return QueryStatus::Loading;
|
||||||
|
};
|
||||||
|
let QueryState { data } = ent.read(cx);
|
||||||
|
match data {
|
||||||
|
QueryData::Loading | QueryData::Pending | QueryData::Stale => QueryStatus::Loading,
|
||||||
|
QueryData::Some(data) => QueryStatus::Loaded(data.downcast_ref::<F::Data>().unwrap()),
|
||||||
|
QueryData::Err(error) => QueryStatus::Err(error.downcast_ref::<F::Error>().unwrap()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ================= Store ==================
|
// ================= Store ==================
|
||||||
|
|
||||||
pub(crate) struct QueryState {
|
pub(crate) struct QueryState {
|
||||||
@@ -61,7 +81,7 @@ pub(crate) enum QueryData {
|
|||||||
|
|
||||||
pub(crate) type Entity = gpui::Entity<QueryState>;
|
pub(crate) type Entity = gpui::Entity<QueryState>;
|
||||||
|
|
||||||
pub(crate) trait Context {}
|
pub(crate) trait Context: Clone {}
|
||||||
|
|
||||||
pub struct Store<C>
|
pub struct Store<C>
|
||||||
where
|
where
|
||||||
@@ -75,7 +95,7 @@ impl<C> Store<C>
|
|||||||
where
|
where
|
||||||
C: Context + 'static,
|
C: Context + 'static,
|
||||||
{
|
{
|
||||||
pub fn new<E>(ctx: C, cx: &mut gpui::Context<E>) -> Self {
|
pub fn new(ctx: C, cx: &mut gpui::App) -> Self {
|
||||||
Self {
|
Self {
|
||||||
query_data: std::collections::HashMap::new(),
|
query_data: std::collections::HashMap::new(),
|
||||||
query_context: cx.new(|_| ctx),
|
query_context: cx.new(|_| ctx),
|
||||||
@@ -131,9 +151,12 @@ where
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let q = query.clone();
|
||||||
|
let query_context = self.query_context.clone();
|
||||||
|
|
||||||
cx.spawn(async move |_, cx| {
|
cx.spawn(async move |_, cx| {
|
||||||
let query_context = self.query_context.read(cx);
|
let c = query_context.read_with(cx, |c, _| c.clone())?;
|
||||||
let result = query.run(query_context).await;
|
let result = q.run(&c).await;
|
||||||
|
|
||||||
entity.update(cx, |state, cx| {
|
entity.update(cx, |state, cx| {
|
||||||
state.data = match result {
|
state.data = match result {
|
||||||
@@ -180,3 +203,5 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<C> gpui::Global for Store<C> where C: Context + 'static {}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ impl Variant {
|
|||||||
border: neutral(800),
|
border: neutral(800),
|
||||||
text: neutral(50),
|
text: neutral(50),
|
||||||
text_muted: neutral(400),
|
text_muted: neutral(400),
|
||||||
accent: violet(400),
|
accent: violet(600),
|
||||||
accent_hover: violet(300),
|
accent_hover: violet(300),
|
||||||
accent_text: neutral(100),
|
accent_text: neutral(100),
|
||||||
success: hex(0x22c55e),
|
success: hex(0x22c55e),
|
||||||
|
|||||||
@@ -1,16 +1,27 @@
|
|||||||
use gpui::{ParentElement, Styled, div, AppContext};
|
use gpui::prelude::FluentBuilder;
|
||||||
|
use gpui::{ParentElement, Styled, div};
|
||||||
|
|
||||||
use crate::{api, app, component::{
|
use crate::component::button::button;
|
||||||
font_icon::{FontIcon, font_icon},
|
use crate::query::{QueryStatus, read_query, use_query};
|
||||||
text::text,
|
use crate::{
|
||||||
}, query};
|
api, app,
|
||||||
use crate::app::query_store;
|
component::{
|
||||||
use crate::query::use_query;
|
font_icon::{FontIcon, font_icon},
|
||||||
|
text::text,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct TitleBar {}
|
pub struct TitleBar {}
|
||||||
|
|
||||||
pub struct RepoSelector {}
|
pub struct RepoSelector {}
|
||||||
|
|
||||||
|
impl TitleBar {
|
||||||
|
pub fn new(cx: &mut gpui::Context<Self>) -> Self {
|
||||||
|
use_query(api::user::Fetch, cx);
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl gpui::Render for TitleBar {
|
impl gpui::Render for TitleBar {
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -18,35 +29,44 @@ impl gpui::Render for TitleBar {
|
|||||||
cx: &mut gpui::Context<Self>,
|
cx: &mut gpui::Context<Self>,
|
||||||
) -> impl gpui::IntoElement {
|
) -> impl gpui::IntoElement {
|
||||||
let g = cx.global::<app::Global>();
|
let g = cx.global::<app::Global>();
|
||||||
|
let user = read_query(api::user::Fetch, cx);
|
||||||
|
|
||||||
|
let user_avatar = match user {
|
||||||
|
QueryStatus::Err(api::Error::Unauthenticated) => div()
|
||||||
|
.absolute()
|
||||||
|
.right_2p5()
|
||||||
|
.child(button("login-btn", cx).label("Login")),
|
||||||
|
|
||||||
|
_ => div(),
|
||||||
|
};
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.w_full()
|
|
||||||
.h_8()
|
|
||||||
.flex()
|
|
||||||
.px(g.safe_area.size.width)
|
|
||||||
.py_2()
|
|
||||||
.flex_row()
|
.flex_row()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.items_center()
|
.items_center()
|
||||||
.border_b_1()
|
.w_full()
|
||||||
.border_color(g.current_theme.colors.border)
|
.h_10()
|
||||||
.bg(g.current_theme.colors.surface)
|
.flex()
|
||||||
|
.px(g.safe_area.size.width)
|
||||||
|
.py_2()
|
||||||
|
.bg(g.current_theme.colors.background)
|
||||||
.text_color(g.current_theme.colors.text)
|
.text_color(g.current_theme.colors.text)
|
||||||
|
.relative()
|
||||||
.child(repo_selector(cx))
|
.child(repo_selector(cx))
|
||||||
|
.child(user_avatar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RepoSelector {
|
impl RepoSelector {
|
||||||
pub fn new(cx: &mut gpui::Context<Self>) -> Self {
|
pub fn new(cx: &mut gpui::Context<Self>) -> Self {
|
||||||
use_query(api::repo::List, cx);
|
use_query(api::repo::List, cx);
|
||||||
|
use_query(api::user::Fetch, cx);
|
||||||
|
|
||||||
Self {}
|
Self {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn repo_selector<T>(cx: &gpui::Context<T>) -> gpui::Div {
|
fn repo_selector<T: 'static>(cx: &gpui::Context<T>) -> gpui::Div {
|
||||||
let store = app::query_store(cx);
|
|
||||||
let repo = store.read_query(api::repo::List, cx);
|
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_row()
|
.flex_row()
|
||||||
|
|||||||
Reference in New Issue
Block a user