wip
This commit is contained in:
@@ -8,3 +8,5 @@ anyhow = "1"
|
|||||||
gpui = { version = "*" }
|
gpui = { version = "*" }
|
||||||
paste = "1.0"
|
paste = "1.0"
|
||||||
reqwest = "0.13.2"
|
reqwest = "0.13.2"
|
||||||
|
serde = "1.0.228"
|
||||||
|
serde_json = "1.0.149"
|
||||||
|
|||||||
25
src/api.rs
25
src/api.rs
@@ -1,5 +1,6 @@
|
|||||||
use crate::query;
|
use crate::query;
|
||||||
|
|
||||||
|
pub(crate) mod auth;
|
||||||
pub(crate) mod repo;
|
pub(crate) mod repo;
|
||||||
pub(crate) mod user;
|
pub(crate) mod user;
|
||||||
|
|
||||||
@@ -7,16 +8,36 @@ pub(crate) mod user;
|
|||||||
pub struct QueryContext {
|
pub struct QueryContext {
|
||||||
pub(crate) http: reqwest::Client,
|
pub(crate) http: reqwest::Client,
|
||||||
pub(crate) auth: Option<Auth>,
|
pub(crate) auth: Option<Auth>,
|
||||||
|
pub(crate) github: GithubCredentials,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct Auth {
|
pub(crate) struct Auth {
|
||||||
pub(crate) access_token: String,
|
pub(crate) access_token: &'static str,
|
||||||
pub(crate) refresh_token: String,
|
pub(crate) refresh_token: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct GithubCredentials {
|
||||||
|
pub(crate) client_id: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Unauthenticated,
|
Unauthenticated,
|
||||||
|
MalformedResponse(serde_json::Error),
|
||||||
|
HttpError(reqwest::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl query::Context for QueryContext {}
|
impl query::Context for QueryContext {}
|
||||||
|
|
||||||
|
impl From<reqwest::Error> for Error {
|
||||||
|
fn from(value: reqwest::Error) -> Self {
|
||||||
|
Self::HttpError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<serde_json::Error> for Error {
|
||||||
|
fn from(value: serde_json::Error) -> Self {
|
||||||
|
Self::MalformedResponse(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
40
src/api/auth.rs
Normal file
40
src/api/auth.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::{api, query};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct CreateDeviceCode;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct DeviceCodeResponse {
|
||||||
|
device_code: String,
|
||||||
|
user_code: String,
|
||||||
|
vertification_uri: String,
|
||||||
|
expires_in: u16,
|
||||||
|
interval: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl query::QueryFn for CreateDeviceCode {
|
||||||
|
type Data = DeviceCodeResponse;
|
||||||
|
type Error = api::Error;
|
||||||
|
type Context = api::QueryContext;
|
||||||
|
|
||||||
|
fn key(&self) -> &'static str {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(&self, c: &Self::Context) -> Result<Self::Data, Self::Error> {
|
||||||
|
let data = c
|
||||||
|
.http
|
||||||
|
.post(format!(
|
||||||
|
"https://github.com/login/device/code?client_id={}",
|
||||||
|
c.github.client_id
|
||||||
|
))
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.bytes()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
serde_json::from_slice::<DeviceCodeResponse>(&data).map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,15 +4,16 @@ use crate::query;
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct List;
|
pub struct List;
|
||||||
|
|
||||||
impl query::QueryFn<QueryContext> for List {
|
impl query::QueryFn for List {
|
||||||
type Data = ();
|
type Data = ();
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
type Context = QueryContext;
|
||||||
|
|
||||||
fn key(&self) -> &'static str {
|
fn key(&self) -> &'static str {
|
||||||
"repo.list"
|
"repo.list"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, _c: &QueryContext) -> Result<Self::Data, Self::Error> {
|
async fn run(&self, _c: &Self::Context) -> Result<Self::Data, Self::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ use crate::{api, query};
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Fetch;
|
pub struct Fetch;
|
||||||
|
|
||||||
impl query::QueryFn<api::QueryContext> for Fetch {
|
impl query::QueryFn for Fetch {
|
||||||
type Data = api::Error;
|
type Data = api::Error;
|
||||||
|
|
||||||
type Error = api::Error;
|
type Error = api::Error;
|
||||||
|
type Context = api::QueryContext;
|
||||||
|
|
||||||
fn key(&self) -> &'static str {
|
fn key(&self) -> &'static str {
|
||||||
"user"
|
"user"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&self, c: &api::QueryContext) -> Result<Self::Data, Self::Error> {
|
async fn run(&self, c: &Self::Context) -> Result<Self::Data, Self::Error> {
|
||||||
Err(api::Error::Unauthenticated)
|
Err(api::Error::Unauthenticated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/asset/font_icon/github.svg
Normal file
1
src/asset/font_icon/github.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
|
||||||
|
After Width: | Height: | Size: 823 B |
@@ -1,14 +1,24 @@
|
|||||||
use gpui::{FontWeight, InteractiveElement, ParentElement, Styled, div};
|
use gpui::{AnyElement, FontWeight, InteractiveElement, ParentElement, Styled, div};
|
||||||
|
|
||||||
use crate::{app, component::text::Text};
|
use crate::{app, component::text::Text};
|
||||||
|
|
||||||
pub struct Button(gpui::Stateful<gpui::Div>);
|
pub struct Button {
|
||||||
|
div: gpui::Stateful<gpui::Div>,
|
||||||
|
text_color: gpui::Rgba,
|
||||||
|
label: Option<gpui::AnyElement>,
|
||||||
|
leading: Option<gpui::Svg>,
|
||||||
|
trailing: Option<gpui::Svg>,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn button<T>(id: impl Into<gpui::ElementId>, cx: &gpui::Context<T>) -> Button {
|
pub fn button<E>(id: impl Into<gpui::ElementId>, cx: &gpui::Context<E>) -> Button {
|
||||||
let theme = app::current_theme(cx);
|
let theme = app::current_theme(cx);
|
||||||
Button(
|
Button {
|
||||||
div()
|
div: div()
|
||||||
.id(id)
|
.id(id)
|
||||||
|
.flex()
|
||||||
|
.flex_row()
|
||||||
|
.gap_2()
|
||||||
|
.items_center()
|
||||||
.rounded_sm()
|
.rounded_sm()
|
||||||
.bg(theme.colors.accent)
|
.bg(theme.colors.accent)
|
||||||
.text_xs()
|
.text_xs()
|
||||||
@@ -16,12 +26,26 @@ pub fn button<T>(id: impl Into<gpui::ElementId>, cx: &gpui::Context<T>) -> Butto
|
|||||||
.font_weight(FontWeight(500.))
|
.font_weight(FontWeight(500.))
|
||||||
.px_2p5()
|
.px_2p5()
|
||||||
.py_0p5(),
|
.py_0p5(),
|
||||||
)
|
text_color: theme.colors.accent_text,
|
||||||
|
label: None,
|
||||||
|
leading: None,
|
||||||
|
trailing: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Button {
|
impl Button {
|
||||||
pub fn label(mut self, s: impl Text) -> Self {
|
pub fn label(mut self, s: impl Text) -> Self {
|
||||||
self.0 = self.0.child(s);
|
self.label = Some(s.into_any_element());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn leading(mut self, s: gpui::Svg) -> Self {
|
||||||
|
self.leading = Some(s.size_3().text_color(self.text_color));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trailing(mut self, s: gpui::Svg) -> Self {
|
||||||
|
self.trailing = Some(s.text_color(self.text_color));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,6 +54,16 @@ impl gpui::IntoElement for Button {
|
|||||||
type Element = gpui::Stateful<gpui::Div>;
|
type Element = gpui::Stateful<gpui::Div>;
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self.0
|
let mut children: Vec<AnyElement> = Vec::with_capacity(3);
|
||||||
|
if let Some(leading) = self.leading {
|
||||||
|
children.push(leading.into_any_element());
|
||||||
|
}
|
||||||
|
if let Some(label) = self.label {
|
||||||
|
children.push(label);
|
||||||
|
}
|
||||||
|
if let Some(trailing) = self.trailing {
|
||||||
|
children.push(trailing.into_any_element());
|
||||||
|
}
|
||||||
|
self.div.children(children)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,10 @@ macro_rules! define_font_icons {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
define_font_icons!(ChevronDown, FolderGit);
|
define_font_icons!(ChevronDown, FolderGit, Github);
|
||||||
|
|
||||||
pub fn font_icon<T>(icon: FontIcon, cx: &gpui::Context<T>) -> gpui::Svg {
|
pub fn font_icon<T>(icon: FontIcon, cx: &gpui::Context<T>) -> gpui::Svg {
|
||||||
let theme = cx.global::<app::Global>().current_theme;
|
let theme = cx.global::<app::Global>().current_theme;
|
||||||
|
println!("{}", icon_path(icon));
|
||||||
svg().path(icon_path(icon)).text_color(theme.colors.text)
|
svg().path(icon_path(icon)).text_color(theme.colors.text)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ mod colors;
|
|||||||
mod component;
|
mod component;
|
||||||
mod dashboard;
|
mod dashboard;
|
||||||
mod query;
|
mod query;
|
||||||
|
mod screen;
|
||||||
mod theme;
|
mod theme;
|
||||||
mod titlebar;
|
mod titlebar;
|
||||||
|
|
||||||
@@ -22,6 +23,9 @@ fn setup_application(cx: &mut gpui::App) {
|
|||||||
api::QueryContext {
|
api::QueryContext {
|
||||||
http: reqwest::Client::new(),
|
http: reqwest::Client::new(),
|
||||||
auth: None,
|
auth: None,
|
||||||
|
github: api::GithubCredentials {
|
||||||
|
client_id: "Iv23liZD4bMQpGJICsR7",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|||||||
161
src/query.rs
161
src/query.rs
@@ -1,16 +1,27 @@
|
|||||||
use gpui::{AppContext, BorrowAppContext};
|
use gpui::{AppContext, BorrowAppContext};
|
||||||
use std::any::Any;
|
use std::{any::Any, collections::HashMap, marker::PhantomData};
|
||||||
|
|
||||||
pub trait QueryFn<C>: Clone + 'static
|
pub trait QueryFn: Clone + 'static {
|
||||||
where
|
|
||||||
C: Context,
|
|
||||||
{
|
|
||||||
type Data: 'static;
|
type Data: 'static;
|
||||||
type Error: 'static;
|
type Error: 'static;
|
||||||
|
type Context: Context;
|
||||||
|
|
||||||
fn key(&self) -> &'static str;
|
fn key(&self) -> &'static str;
|
||||||
|
|
||||||
async fn run(&self, c: &C) -> Result<Self::Data, Self::Error>;
|
async fn run(&self, c: &Self::Context) -> Result<Self::Data, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Query {
|
||||||
|
key: &'static str,
|
||||||
|
data: QueryData,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum QueryData {
|
||||||
|
Pending,
|
||||||
|
Loading,
|
||||||
|
Stale,
|
||||||
|
Some(Box<dyn Any>),
|
||||||
|
Err(Box<dyn Any>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum QueryStatus<'a, Data, Error> {
|
pub enum QueryStatus<'a, Data, Error> {
|
||||||
@@ -19,46 +30,77 @@ pub enum QueryStatus<'a, Data, Error> {
|
|||||||
Err(&'a Error),
|
Err(&'a Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn use_query<F, T, C>(query_fn: F, cx: &mut gpui::Context<T>)
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct Entity<F>
|
||||||
where
|
where
|
||||||
C: Context + 'static,
|
F: QueryFn,
|
||||||
F: QueryFn<C>,
|
|
||||||
T: 'static,
|
|
||||||
Store<C>: gpui::Global,
|
|
||||||
{
|
{
|
||||||
let ent = cx.update_global::<Store<C>, _>(|store, cx| store.ensure_query_data(&query_fn, cx));
|
raw: gpui::Entity<Query>,
|
||||||
|
_marker: PhantomData<fn() -> F>,
|
||||||
|
}
|
||||||
|
|
||||||
cx.observe(&ent, |_, _, cx| {
|
pub fn use_query<F, T>(query_fn: F, cx: &mut gpui::Context<T>) -> Entity<F>
|
||||||
|
where
|
||||||
|
F: QueryFn,
|
||||||
|
T: 'static,
|
||||||
|
Store<F::Context>: gpui::Global,
|
||||||
|
{
|
||||||
|
let ent = cx
|
||||||
|
.update_global::<Store<F::Context>, _>(|store, cx| store.ensure_query_data(&query_fn, cx));
|
||||||
|
|
||||||
|
cx.observe(&ent.raw, |_, _, cx| {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let query_context = cx.global::<Store<C>>().query_context.clone();
|
let query_context = cx.global::<Store<F::Context>>().query_context.clone();
|
||||||
|
|
||||||
cx.observe(&query_context, move |_, _, cx| {
|
cx.observe(&query_context, move |_, _, cx| {
|
||||||
cx.update_global::<Store<C>, _>(|store, cx| {
|
cx.update_global::<Store<F::Context>, _>(|store, cx| {
|
||||||
store.invalidate_query(&query_fn, cx);
|
store.invalidate_query(&query_fn, cx);
|
||||||
store.ensure_query_data(&query_fn, cx);
|
store.ensure_query_data(&query_fn, cx);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
ent
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_query<'a, F, T, C>(
|
pub fn use_lazy_query<F, T>(query_fn: F, cx: &mut gpui::Context<T>) -> Entity<F>
|
||||||
query_fn: F,
|
where
|
||||||
|
F: QueryFn,
|
||||||
|
T: 'static,
|
||||||
|
Store<F::Context>: gpui::Global,
|
||||||
|
{
|
||||||
|
let ent = cx.update_global::<Store<F::Context>, _>(|store, cx| store.entity_for(&query_fn, cx));
|
||||||
|
|
||||||
|
cx.observe(&ent.raw, |_, ent, cx| {
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
let query_context = cx.global::<Store<F::Context>>().query_context.clone();
|
||||||
|
cx.observe(&query_context, move |_, _, cx| {
|
||||||
|
cx.update_global::<Store<F::Context>, _>(|store, cx| {
|
||||||
|
store.invalidate_query(&query_fn, cx);
|
||||||
|
store.ensure_query_data(&query_fn, cx);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
ent
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_query<'a, F, T>(
|
||||||
|
query: &Entity<F>,
|
||||||
cx: &'a gpui::Context<T>,
|
cx: &'a gpui::Context<T>,
|
||||||
) -> QueryStatus<'a, F::Data, F::Error>
|
) -> QueryStatus<'a, F::Data, F::Error>
|
||||||
where
|
where
|
||||||
C: Context + 'static,
|
F: QueryFn,
|
||||||
F: QueryFn<C>,
|
|
||||||
T: 'static,
|
T: 'static,
|
||||||
{
|
{
|
||||||
let store = cx.global::<Store<C>>();
|
let state = query.raw.read(cx);
|
||||||
let Some(ent) = store.query_data.get(query_fn.key()) else {
|
|
||||||
return QueryStatus::Loading;
|
match &state.data {
|
||||||
};
|
|
||||||
let QueryState { data } = ent.read(cx);
|
|
||||||
match data {
|
|
||||||
QueryData::Loading | QueryData::Pending | QueryData::Stale => QueryStatus::Loading,
|
QueryData::Loading | QueryData::Pending | QueryData::Stale => QueryStatus::Loading,
|
||||||
QueryData::Some(data) => QueryStatus::Loaded(data.downcast_ref::<F::Data>().unwrap()),
|
QueryData::Some(data) => QueryStatus::Loaded(data.downcast_ref::<F::Data>().unwrap()),
|
||||||
QueryData::Err(error) => QueryStatus::Err(error.downcast_ref::<F::Error>().unwrap()),
|
QueryData::Err(error) => QueryStatus::Err(error.downcast_ref::<F::Error>().unwrap()),
|
||||||
@@ -67,27 +109,13 @@ where
|
|||||||
|
|
||||||
// ================= Store ==================
|
// ================= Store ==================
|
||||||
|
|
||||||
pub(crate) struct QueryState {
|
|
||||||
data: QueryData,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) enum QueryData {
|
|
||||||
Pending,
|
|
||||||
Loading,
|
|
||||||
Stale,
|
|
||||||
Some(Box<dyn Any>),
|
|
||||||
Err(Box<dyn Any>),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) type Entity = gpui::Entity<QueryState>;
|
|
||||||
|
|
||||||
pub(crate) trait Context: Clone {}
|
pub(crate) trait Context: Clone {}
|
||||||
|
|
||||||
pub struct Store<C>
|
pub struct Store<C>
|
||||||
where
|
where
|
||||||
C: Context,
|
C: Context,
|
||||||
{
|
{
|
||||||
query_data: std::collections::HashMap<String, Entity>,
|
query_data: HashMap<String, gpui::Entity<Query>>,
|
||||||
query_context: gpui::Entity<C>,
|
query_context: gpui::Entity<C>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,29 +130,36 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entity_for<Q, T>(&mut self, query: &Q, cx: &mut gpui::Context<T>) -> Entity
|
fn entity_for<Q, T>(&mut self, query: &Q, cx: &mut gpui::Context<T>) -> Entity<Q>
|
||||||
where
|
where
|
||||||
Q: QueryFn<C>,
|
Q: QueryFn<Context = C>,
|
||||||
T: 'static,
|
T: 'static,
|
||||||
{
|
{
|
||||||
self.query_data
|
let raw = self
|
||||||
|
.query_data
|
||||||
.entry(query.key().into())
|
.entry(query.key().into())
|
||||||
.or_insert_with(|| {
|
.or_insert_with(|| {
|
||||||
cx.new(|_| QueryState {
|
cx.new(|_| Query {
|
||||||
|
key: query.key(),
|
||||||
data: QueryData::Pending,
|
data: QueryData::Pending,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.clone()
|
.clone();
|
||||||
|
|
||||||
|
Entity {
|
||||||
|
raw,
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_query_data<Q, T>(&mut self, query: &Q, cx: &mut gpui::Context<T>) -> Entity
|
fn ensure_query_data<Q, T>(&mut self, query: &Q, cx: &mut gpui::Context<T>) -> Entity<Q>
|
||||||
where
|
where
|
||||||
T: 'static,
|
T: 'static,
|
||||||
Q: QueryFn<C>,
|
Q: QueryFn<Context = C>,
|
||||||
{
|
{
|
||||||
let entity = self.entity_for(query, cx);
|
let entity = self.entity_for(query, cx);
|
||||||
|
|
||||||
let should_execute = entity.read_with(cx, |state, _| {
|
let should_execute = entity.raw.read_with(cx, |state, _| {
|
||||||
matches!(state.data, QueryData::Pending | QueryData::Stale)
|
matches!(state.data, QueryData::Pending | QueryData::Stale)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -141,12 +176,12 @@ where
|
|||||||
cx: &mut gpui::Context<T>,
|
cx: &mut gpui::Context<T>,
|
||||||
) -> gpui::Task<anyhow::Result<()>>
|
) -> gpui::Task<anyhow::Result<()>>
|
||||||
where
|
where
|
||||||
Q: QueryFn<C>,
|
Q: QueryFn<Context = C>,
|
||||||
T: 'static,
|
T: 'static,
|
||||||
{
|
{
|
||||||
let entity = self.entity_for(query, cx);
|
let entity = self.entity_for(query, cx);
|
||||||
|
|
||||||
entity.update(cx, |state, cx| {
|
entity.raw.update(cx, |state, cx| {
|
||||||
state.data = QueryData::Loading;
|
state.data = QueryData::Loading;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
@@ -158,7 +193,7 @@ where
|
|||||||
let c = query_context.read_with(cx, |c, _| c.clone())?;
|
let c = query_context.read_with(cx, |c, _| c.clone())?;
|
||||||
let result = q.run(&c).await;
|
let result = q.run(&c).await;
|
||||||
|
|
||||||
entity.update(cx, |state, cx| {
|
entity.raw.update(cx, |state, cx| {
|
||||||
state.data = match result {
|
state.data = match result {
|
||||||
Ok(data) => QueryData::Some(Box::new(data)),
|
Ok(data) => QueryData::Some(Box::new(data)),
|
||||||
Err(err) => QueryData::Err(Box::new(err)),
|
Err(err) => QueryData::Err(Box::new(err)),
|
||||||
@@ -172,7 +207,8 @@ where
|
|||||||
|
|
||||||
fn invalidate_query<Q, T>(&mut self, query: &Q, cx: &mut gpui::Context<T>)
|
fn invalidate_query<Q, T>(&mut self, query: &Q, cx: &mut gpui::Context<T>)
|
||||||
where
|
where
|
||||||
Q: QueryFn<C>,
|
Q: QueryFn<Context = C>,
|
||||||
|
T: 'static,
|
||||||
{
|
{
|
||||||
if let Some(entity) = self.query_data.get(query.key()) {
|
if let Some(entity) = self.query_data.get(query.key()) {
|
||||||
entity.update(cx, |query, cx| {
|
entity.update(cx, |query, cx| {
|
||||||
@@ -183,25 +219,6 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn read_query<'a, Q, E>(
|
|
||||||
&self,
|
|
||||||
query_fn: Q,
|
|
||||||
cx: &'a gpui::Context<E>,
|
|
||||||
) -> QueryStatus<'a, Q::Data, Q::Error>
|
|
||||||
where
|
|
||||||
Q: QueryFn<C>,
|
|
||||||
{
|
|
||||||
let Some(ent) = self.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::<Q::Data>().unwrap()),
|
|
||||||
QueryData::Err(error) => QueryStatus::Err(error.downcast_ref::<Q::Error>().unwrap()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C> gpui::Global for Store<C> where C: Context + 'static {}
|
impl<C> gpui::Global for Store<C> where C: Context + 'static {}
|
||||||
|
|||||||
1
src/screen.rs
Normal file
1
src/screen.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub(crate) mod welcome;
|
||||||
11
src/screen/welcome.rs
Normal file
11
src/screen/welcome.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
struct Welcome {}
|
||||||
|
|
||||||
|
impl gpui::Render for Welcome {
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
window: &mut gpui::Window,
|
||||||
|
cx: &mut gpui::Context<Self>,
|
||||||
|
) -> impl gpui::IntoElement {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ use gpui::prelude::FluentBuilder;
|
|||||||
use gpui::{ParentElement, Styled, div};
|
use gpui::{ParentElement, Styled, div};
|
||||||
|
|
||||||
use crate::component::button::button;
|
use crate::component::button::button;
|
||||||
use crate::query::{QueryStatus, read_query, use_query};
|
use crate::query::{self, QueryStatus, read_query, use_query};
|
||||||
use crate::{
|
use crate::{
|
||||||
api, app,
|
api, app,
|
||||||
component::{
|
component::{
|
||||||
@@ -11,14 +11,17 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct TitleBar {}
|
pub struct TitleBar {
|
||||||
|
fetch_user_query: query::Entity<api::user::Fetch>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RepoSelector {}
|
pub struct RepoSelector {}
|
||||||
|
|
||||||
impl TitleBar {
|
impl TitleBar {
|
||||||
pub fn new(cx: &mut gpui::Context<Self>) -> Self {
|
pub fn new(cx: &mut gpui::Context<Self>) -> Self {
|
||||||
use_query(api::user::Fetch, cx);
|
Self {
|
||||||
Self {}
|
fetch_user_query: use_query(api::user::Fetch, cx),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,13 +32,14 @@ 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 = read_query(&self.fetch_user_query, cx);
|
||||||
|
|
||||||
let user_avatar = match user {
|
let user_avatar = match user {
|
||||||
QueryStatus::Err(api::Error::Unauthenticated) => div()
|
QueryStatus::Err(api::Error::Unauthenticated) => div().absolute().right_2p5().child(
|
||||||
.absolute()
|
button("login-btn", cx)
|
||||||
.right_2p5()
|
.leading(font_icon(FontIcon::Github, cx))
|
||||||
.child(button("login-btn", cx).label("Login")),
|
.label("Login"),
|
||||||
|
),
|
||||||
|
|
||||||
_ => div(),
|
_ => div(),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user