some more bs
This commit is contained in:
15
src/api.rs
15
src/api.rs
@@ -1,9 +1,22 @@
|
||||
use crate::query;
|
||||
|
||||
pub(crate) mod repo;
|
||||
pub(crate) mod user;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct QueryContext {
|
||||
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"
|
||||
}
|
||||
|
||||
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 crate::{api, app};
|
||||
use crate::query;
|
||||
use crate::dashboard;
|
||||
use crate::query;
|
||||
use crate::theme;
|
||||
use crate::titlebar;
|
||||
use crate::{api, app};
|
||||
|
||||
pub struct Global {
|
||||
pub safe_area: gpui::Bounds<gpui::Pixels>,
|
||||
pub current_theme: theme::Theme,
|
||||
pub query_store: query::Store<api::QueryContext>
|
||||
}
|
||||
|
||||
pub struct Chrome {}
|
||||
@@ -17,8 +16,9 @@ pub struct Chrome {}
|
||||
impl Chrome {
|
||||
pub fn new(window: &mut gpui::Window, cx: &mut gpui::Context<Self>) -> Self {
|
||||
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();
|
||||
cx.notify();
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
@@ -33,15 +33,16 @@ impl gpui::Render for Chrome {
|
||||
_window: &mut gpui::Window,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> 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 {
|
||||
text: "World".into(),
|
||||
});
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.size_full()
|
||||
.child(title_bar)
|
||||
.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> {
|
||||
&cx.global::<Global>().query_store
|
||||
cx.global::<query::Store<api::QueryContext>>()
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod button;
|
||||
pub mod font_icon;
|
||||
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 crate::theme::Variant;
|
||||
use crate::{app, theme::Variant};
|
||||
|
||||
pub struct Screen {
|
||||
pub text: gpui::SharedString,
|
||||
@@ -10,39 +10,39 @@ impl Render for Screen {
|
||||
fn render(
|
||||
&mut self,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut gpui::Context<Self>,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let builtin = Variant::VioletDark;
|
||||
let theme = builtin.theme();
|
||||
let theme = app::current_theme(cx);
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.size_full()
|
||||
.gap_3()
|
||||
.flex_1()
|
||||
.flex_row()
|
||||
.w_full()
|
||||
.gap_2()
|
||||
.p_2p5()
|
||||
.pt_0()
|
||||
.bg(theme.colors.background)
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.shadow_lg()
|
||||
.text_xl()
|
||||
.text_color(theme.colors.text)
|
||||
.child(format!("Hello, {}!", &self.text))
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme.colors.text_muted)
|
||||
.child(format!("Built-in theme: {}", builtin.label())),
|
||||
.h_full()
|
||||
.flex()
|
||||
.w_1_3()
|
||||
.bg(theme.colors.surface)
|
||||
.rounded_lg(),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.h_full()
|
||||
.flex()
|
||||
.gap_2()
|
||||
.child(div().size_8().bg(theme.colors.surface))
|
||||
.child(div().size_8().bg(theme.colors.surface_elevated))
|
||||
.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)),
|
||||
.w_2_3()
|
||||
.bg(theme.colors.surface)
|
||||
.rounded_lg(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
15
src/main.rs
15
src/main.rs
@@ -1,5 +1,6 @@
|
||||
use gpui::{bounds, point, prelude::*, px, size};
|
||||
|
||||
mod api;
|
||||
mod app;
|
||||
mod asset;
|
||||
mod colors;
|
||||
@@ -8,7 +9,6 @@ mod dashboard;
|
||||
mod query;
|
||||
mod theme;
|
||||
mod titlebar;
|
||||
mod api;
|
||||
|
||||
fn main() {
|
||||
gpui::Application::new()
|
||||
@@ -18,25 +18,30 @@ fn main() {
|
||||
|
||||
fn setup_application(cx: &mut gpui::App) {
|
||||
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 {
|
||||
safe_area: bounds(point(px(0.), px(0.)), size(px(72.), px(12.))),
|
||||
current_theme: cx.window_appearance().into(),
|
||||
query_store: query::Store::new(api::QueryContext {
|
||||
http: reqwest::Client::new(),
|
||||
}),
|
||||
};
|
||||
|
||||
let top_left = global.safe_area.origin;
|
||||
|
||||
cx.set_global(global);
|
||||
cx.set_global(query_store);
|
||||
|
||||
cx.open_window(
|
||||
gpui::WindowOptions {
|
||||
window_bounds: Some(gpui::WindowBounds::Windowed(window_bounds)),
|
||||
titlebar: Some(gpui::TitlebarOptions {
|
||||
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()
|
||||
|
||||
37
src/query.rs
37
src/query.rs
@@ -1,10 +1,9 @@
|
||||
use crate::app;
|
||||
use gpui::{AppContext, BorrowAppContext};
|
||||
use std::any::Any;
|
||||
|
||||
pub trait QueryFn<C>: Clone + 'static
|
||||
where
|
||||
C: Context + 'static,
|
||||
C: Context,
|
||||
{
|
||||
type Data: 'static;
|
||||
type Error: 'static;
|
||||
@@ -45,6 +44,27 @@ where
|
||||
.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 ==================
|
||||
|
||||
pub(crate) struct QueryState {
|
||||
@@ -61,7 +81,7 @@ pub(crate) enum QueryData {
|
||||
|
||||
pub(crate) type Entity = gpui::Entity<QueryState>;
|
||||
|
||||
pub(crate) trait Context {}
|
||||
pub(crate) trait Context: Clone {}
|
||||
|
||||
pub struct Store<C>
|
||||
where
|
||||
@@ -75,7 +95,7 @@ impl<C> Store<C>
|
||||
where
|
||||
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 {
|
||||
query_data: std::collections::HashMap::new(),
|
||||
query_context: cx.new(|_| ctx),
|
||||
@@ -131,9 +151,12 @@ where
|
||||
cx.notify();
|
||||
});
|
||||
|
||||
let q = query.clone();
|
||||
let query_context = self.query_context.clone();
|
||||
|
||||
cx.spawn(async move |_, cx| {
|
||||
let query_context = self.query_context.read(cx);
|
||||
let result = query.run(query_context).await;
|
||||
let c = query_context.read_with(cx, |c, _| c.clone())?;
|
||||
let result = q.run(&c).await;
|
||||
|
||||
entity.update(cx, |state, cx| {
|
||||
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),
|
||||
text: neutral(50),
|
||||
text_muted: neutral(400),
|
||||
accent: violet(400),
|
||||
accent: violet(600),
|
||||
accent_hover: violet(300),
|
||||
accent_text: neutral(100),
|
||||
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::{
|
||||
font_icon::{FontIcon, font_icon},
|
||||
text::text,
|
||||
}, query};
|
||||
use crate::app::query_store;
|
||||
use crate::query::use_query;
|
||||
use crate::component::button::button;
|
||||
use crate::query::{QueryStatus, read_query, use_query};
|
||||
use crate::{
|
||||
api, app,
|
||||
component::{
|
||||
font_icon::{FontIcon, font_icon},
|
||||
text::text,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct TitleBar {}
|
||||
|
||||
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 {
|
||||
fn render(
|
||||
&mut self,
|
||||
@@ -18,35 +29,44 @@ impl gpui::Render for TitleBar {
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
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()
|
||||
.w_full()
|
||||
.h_8()
|
||||
.flex()
|
||||
.px(g.safe_area.size.width)
|
||||
.py_2()
|
||||
.flex_row()
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.border_b_1()
|
||||
.border_color(g.current_theme.colors.border)
|
||||
.bg(g.current_theme.colors.surface)
|
||||
.w_full()
|
||||
.h_10()
|
||||
.flex()
|
||||
.px(g.safe_area.size.width)
|
||||
.py_2()
|
||||
.bg(g.current_theme.colors.background)
|
||||
.text_color(g.current_theme.colors.text)
|
||||
.relative()
|
||||
.child(repo_selector(cx))
|
||||
.child(user_avatar)
|
||||
}
|
||||
}
|
||||
|
||||
impl RepoSelector {
|
||||
pub fn new(cx: &mut gpui::Context<Self>) -> Self {
|
||||
use_query(api::repo::List, cx);
|
||||
use_query(api::user::Fetch, cx);
|
||||
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
fn repo_selector<T>(cx: &gpui::Context<T>) -> gpui::Div {
|
||||
let store = app::query_store(cx);
|
||||
let repo = store.read_query(api::repo::List, cx);
|
||||
|
||||
fn repo_selector<T: 'static>(cx: &gpui::Context<T>) -> gpui::Div {
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
|
||||
Reference in New Issue
Block a user