refactor: redesign theme tokens and split catppuccin themes
This commit is contained in:
10
build.rs
10
build.rs
@@ -229,8 +229,8 @@ fn render_github_fixtures(fixture_root: &Path) -> String {
|
||||
output.push_str(&string_literal(&id));
|
||||
output.push_str(", ");
|
||||
match previous_end_cursor.as_deref() {
|
||||
Some(after) => output.push_str(&format!("Some({})", string_literal(after))),
|
||||
None => output.push_str("None"),
|
||||
| Some(after) => output.push_str(&format!("Some({})", string_literal(after))),
|
||||
| None => output.push_str("None"),
|
||||
}
|
||||
output.push_str(") => Some(");
|
||||
output.push_str(&string_literal(&fixture.json));
|
||||
@@ -343,9 +343,9 @@ fn issue_fixture_state(issue: &serde_json::Value) -> &'static str {
|
||||
}
|
||||
|
||||
match required_string(issue, &["state"]) {
|
||||
"open" => "OPEN",
|
||||
"closed" => "CLOSED",
|
||||
state => panic!("unsupported pull request state in fixture: {state}"),
|
||||
| "open" => "OPEN",
|
||||
| "closed" => "CLOSED",
|
||||
| state => panic!("unsupported pull request state in fixture: {state}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ where
|
||||
{
|
||||
let mut body: graphql_client::Response<T> = res.json().await?;
|
||||
match body.data.take() {
|
||||
None => Err(Error::GraphQLError(body.errors.unwrap_or_default())),
|
||||
Some(data) => Ok((body, data)),
|
||||
| None => Err(Error::GraphQLError(body.errors.unwrap_or_default())),
|
||||
| Some(data) => Ok((body, data)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,8 +289,8 @@ impl query::QueryFn for ListPullRequests {
|
||||
}
|
||||
|
||||
let query_string = match self.filter {
|
||||
| Some(filter) => format!("is:pr archived:false sort:updated-desc {}", filter),
|
||||
| None => "is:pr archived:false sort:updated-desc".into(),
|
||||
| Some(filter) => format!("is:pr archived:false sort:updated-desc {}", filter),
|
||||
| None => "is:pr archived:false sort:updated-desc".into(),
|
||||
};
|
||||
|
||||
let gql =
|
||||
@@ -312,19 +312,19 @@ impl query::QueryFn for ListPullRequests {
|
||||
.flatten()
|
||||
.filter_map(|edge| {
|
||||
edge.node.and_then(|n| match n {
|
||||
| PullRequestPaginationQuerySearchEdgesNode::PullRequest(p) => {
|
||||
Some(PullRequest {
|
||||
id: p.id.into(),
|
||||
title: p.title,
|
||||
state: p.state,
|
||||
is_draft: p.is_draft,
|
||||
repo_slug: format!(
|
||||
"{}/{}",
|
||||
p.repository.owner.login, p.repository.name
|
||||
),
|
||||
})
|
||||
}
|
||||
| _ => None,
|
||||
| PullRequestPaginationQuerySearchEdgesNode::PullRequest(p) => {
|
||||
Some(PullRequest {
|
||||
id: p.id.into(),
|
||||
title: p.title,
|
||||
state: p.state,
|
||||
is_draft: p.is_draft,
|
||||
repo_slug: format!(
|
||||
"{}/{}",
|
||||
p.repository.owner.login, p.repository.name
|
||||
),
|
||||
})
|
||||
}
|
||||
| _ => None,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
@@ -369,32 +369,32 @@ impl query::QueryFn for FetchPullRequest {
|
||||
"missing 'node' field on PullRequestQuery response".into(),
|
||||
))
|
||||
.and_then(|n| match n {
|
||||
| PullRequestQueryNode::PullRequest(p) => {
|
||||
let created_at =
|
||||
chrono::DateTime::parse_from_rfc3339(&p.created_at).map_err(|err| {
|
||||
api::Error::MalformedResponse(format!(
|
||||
"invalid pull request createdAt {:?}: {err}",
|
||||
p.created_at
|
||||
))
|
||||
})?;
|
||||
| PullRequestQueryNode::PullRequest(p) => {
|
||||
let created_at =
|
||||
chrono::DateTime::parse_from_rfc3339(&p.created_at).map_err(|err| {
|
||||
api::Error::MalformedResponse(format!(
|
||||
"invalid pull request createdAt {:?}: {err}",
|
||||
p.created_at
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok(DetailedPullRequest {
|
||||
title: p.title,
|
||||
state: p.state,
|
||||
is_draft: p.is_draft,
|
||||
body: p.body,
|
||||
author: p.author.map(|it| api::user::Actor {
|
||||
login: it.login,
|
||||
avatar_url: it.avatar_url,
|
||||
}),
|
||||
base_branch_name: p.base_ref.map(|r| r.name),
|
||||
head_branch_name: p.head_ref.map(|r| r.name),
|
||||
created_at: Some(created_at),
|
||||
})
|
||||
}
|
||||
| _ => Err(api::Error::MalformedResponse(
|
||||
"unexpected node type on PullRequestQuery".into(),
|
||||
)),
|
||||
Ok(DetailedPullRequest {
|
||||
title: p.title,
|
||||
state: p.state,
|
||||
is_draft: p.is_draft,
|
||||
body: p.body,
|
||||
author: p.author.map(|it| api::user::Actor {
|
||||
login: it.login,
|
||||
avatar_url: it.avatar_url,
|
||||
}),
|
||||
base_branch_name: p.base_ref.map(|r| r.name),
|
||||
head_branch_name: p.head_ref.map(|r| r.name),
|
||||
created_at: Some(created_at),
|
||||
})
|
||||
}
|
||||
| _ => Err(api::Error::MalformedResponse(
|
||||
"unexpected node type on PullRequestQuery".into(),
|
||||
)),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -437,11 +437,11 @@ impl query::QueryFn for FetchPullRequestTimeline {
|
||||
|
||||
TimelineActor {
|
||||
kind: match on {
|
||||
| actorFieldsOn::Bot => "Bot",
|
||||
| actorFieldsOn::EnterpriseUserAccount => "EnterpriseUserAccount",
|
||||
| actorFieldsOn::Mannequin => "Mannequin",
|
||||
| actorFieldsOn::Organization => "Organization",
|
||||
| actorFieldsOn::User => "User",
|
||||
| actorFieldsOn::Bot => "Bot",
|
||||
| actorFieldsOn::EnterpriseUserAccount => "EnterpriseUserAccount",
|
||||
| actorFieldsOn::Mannequin => "Mannequin",
|
||||
| actorFieldsOn::Organization => "Organization",
|
||||
| actorFieldsOn::User => "User",
|
||||
}
|
||||
.into(),
|
||||
name: login,
|
||||
@@ -451,62 +451,62 @@ impl query::QueryFn for FetchPullRequestTimeline {
|
||||
|
||||
fn normalize_assignee(actor: assigneeFields) -> TimelineActor {
|
||||
match actor {
|
||||
| assigneeFields::Bot(actor) => TimelineActor {
|
||||
kind: "Bot".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
},
|
||||
| assigneeFields::Mannequin(actor) => TimelineActor {
|
||||
kind: "Mannequin".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
},
|
||||
| assigneeFields::Organization(actor) => TimelineActor {
|
||||
kind: "Organization".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
},
|
||||
| assigneeFields::User(actor) => TimelineActor {
|
||||
kind: "User".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
},
|
||||
| assigneeFields::Bot(actor) => TimelineActor {
|
||||
kind: "Bot".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
},
|
||||
| assigneeFields::Mannequin(actor) => TimelineActor {
|
||||
kind: "Mannequin".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
},
|
||||
| assigneeFields::Organization(actor) => TimelineActor {
|
||||
kind: "Organization".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
},
|
||||
| assigneeFields::User(actor) => TimelineActor {
|
||||
kind: "User".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_requested_reviewer(actor: requestedReviewerFields) -> TimelineActor {
|
||||
match actor {
|
||||
| requestedReviewerFields::Bot(actor) => TimelineActor {
|
||||
kind: "Bot".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
},
|
||||
| requestedReviewerFields::Mannequin(actor) => TimelineActor {
|
||||
kind: "Mannequin".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
},
|
||||
| requestedReviewerFields::Team(actor) => TimelineActor {
|
||||
kind: "Team".into(),
|
||||
name: actor.name,
|
||||
avatar_url: None,
|
||||
},
|
||||
| requestedReviewerFields::User(actor) => TimelineActor {
|
||||
kind: "User".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
},
|
||||
| requestedReviewerFields::Bot(actor) => TimelineActor {
|
||||
kind: "Bot".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
},
|
||||
| requestedReviewerFields::Mannequin(actor) => TimelineActor {
|
||||
kind: "Mannequin".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
},
|
||||
| requestedReviewerFields::Team(actor) => TimelineActor {
|
||||
kind: "Team".into(),
|
||||
name: actor.name,
|
||||
avatar_url: None,
|
||||
},
|
||||
| requestedReviewerFields::User(actor) => TimelineActor {
|
||||
kind: "User".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_review_state(state: PullRequestReviewState) -> String {
|
||||
match state {
|
||||
| PullRequestReviewState::PENDING => "PENDING",
|
||||
| PullRequestReviewState::COMMENTED => "COMMENTED",
|
||||
| PullRequestReviewState::APPROVED => "APPROVED",
|
||||
| PullRequestReviewState::CHANGES_REQUESTED => "CHANGES_REQUESTED",
|
||||
| PullRequestReviewState::DISMISSED => "DISMISSED",
|
||||
| _ => "OTHER",
|
||||
| PullRequestReviewState::PENDING => "PENDING",
|
||||
| PullRequestReviewState::COMMENTED => "COMMENTED",
|
||||
| PullRequestReviewState::APPROVED => "APPROVED",
|
||||
| PullRequestReviewState::CHANGES_REQUESTED => "CHANGES_REQUESTED",
|
||||
| PullRequestReviewState::DISMISSED => "DISMISSED",
|
||||
| _ => "OTHER",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
@@ -726,10 +726,10 @@ impl query::QueryFn for FetchPullRequestTimeline {
|
||||
"missing 'node' field on PullRequestTimelineQuery response".into(),
|
||||
))
|
||||
.and_then(|node| match node {
|
||||
| PullRequestTimelineQueryNode::PullRequest(pull_request) => Ok(pull_request),
|
||||
| _ => Err(api::Error::MalformedResponse(
|
||||
"unexpected node type on PullRequestTimelineQuery".into(),
|
||||
)),
|
||||
| PullRequestTimelineQueryNode::PullRequest(pull_request) => Ok(pull_request),
|
||||
| _ => Err(api::Error::MalformedResponse(
|
||||
"unexpected node type on PullRequestTimelineQuery".into(),
|
||||
)),
|
||||
})?;
|
||||
|
||||
let timeline = pull_request.timeline_items;
|
||||
|
||||
@@ -167,7 +167,10 @@ mod tests {
|
||||
.map(|author| author.login.as_str()),
|
||||
Some("kennethnym")
|
||||
);
|
||||
assert_eq!(documented_failover.base_branch_name.as_deref(), Some("main"));
|
||||
assert_eq!(
|
||||
documented_failover.base_branch_name.as_deref(),
|
||||
Some("main")
|
||||
);
|
||||
assert_eq!(
|
||||
documented_failover.head_branch_name.as_deref(),
|
||||
Some("docs/manual-failover-steps")
|
||||
@@ -203,7 +206,10 @@ mod tests {
|
||||
Some(chrono::DateTime::parse_from_rfc3339("2026-05-03T07:40:00Z").unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
worker_split.author.as_ref().map(|author| author.login.as_str()),
|
||||
worker_split
|
||||
.author
|
||||
.as_ref()
|
||||
.map(|author| author.login.as_str()),
|
||||
Some("leaferiksen")
|
||||
);
|
||||
assert_eq!(worker_split.base_branch_name.as_deref(), Some("main"));
|
||||
@@ -295,36 +301,36 @@ mod tests {
|
||||
.expect("third timeline fixture json should parse");
|
||||
|
||||
let first_page_nodes = match first_page.node.as_ref() {
|
||||
Some(issues::PullRequestTimelineResponseNode::PullRequest(pull_request)) => {
|
||||
| Some(issues::PullRequestTimelineResponseNode::PullRequest(pull_request)) => {
|
||||
pull_request
|
||||
.timeline_items
|
||||
.nodes
|
||||
.as_ref()
|
||||
.expect("first timeline fixture page should contain timeline nodes")
|
||||
}
|
||||
_ => panic!("first timeline fixture page should resolve to a pull request node"),
|
||||
| _ => panic!("first timeline fixture page should resolve to a pull request node"),
|
||||
};
|
||||
|
||||
let second_page_nodes = match second_page.node.as_ref() {
|
||||
Some(issues::PullRequestTimelineResponseNode::PullRequest(pull_request)) => {
|
||||
| Some(issues::PullRequestTimelineResponseNode::PullRequest(pull_request)) => {
|
||||
pull_request
|
||||
.timeline_items
|
||||
.nodes
|
||||
.as_ref()
|
||||
.expect("second timeline fixture page should contain timeline nodes")
|
||||
}
|
||||
_ => panic!("second timeline fixture page should resolve to a pull request node"),
|
||||
| _ => panic!("second timeline fixture page should resolve to a pull request node"),
|
||||
};
|
||||
|
||||
let third_page_nodes = match third_page.node.as_ref() {
|
||||
Some(issues::PullRequestTimelineResponseNode::PullRequest(pull_request)) => {
|
||||
| Some(issues::PullRequestTimelineResponseNode::PullRequest(pull_request)) => {
|
||||
pull_request
|
||||
.timeline_items
|
||||
.nodes
|
||||
.as_ref()
|
||||
.expect("third timeline fixture page should contain timeline nodes")
|
||||
}
|
||||
_ => panic!("third timeline fixture page should resolve to a pull request node"),
|
||||
| _ => panic!("third timeline fixture page should resolve to a pull request node"),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
|
||||
107
src/colors.rs
107
src/colors.rs
@@ -11,74 +11,85 @@ pub const fn hex(hex: u32) -> Rgba {
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn hex_alpha(hex: u32, alpha: f32) -> Rgba {
|
||||
let [_, r, g, b] = hex.to_be_bytes();
|
||||
|
||||
Rgba {
|
||||
r: r as f32 / 255.0,
|
||||
g: g as f32 / 255.0,
|
||||
b: b as f32 / 255.0,
|
||||
a: alpha,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const fn neutral(shade: u16) -> Rgba {
|
||||
match shade {
|
||||
50 => hex(0xfafafa),
|
||||
100 => hex(0xf5f5f5),
|
||||
200 => hex(0xe5e5e5),
|
||||
300 => hex(0xd4d4d4),
|
||||
400 => hex(0xa3a3a3),
|
||||
500 => hex(0x737373),
|
||||
600 => hex(0x525252),
|
||||
700 => hex(0x404040),
|
||||
800 => hex(0x262626),
|
||||
900 => hex(0x171717),
|
||||
950 => hex(0x0a0a0a),
|
||||
_ => panic!("unsupported Tailwind neutral shade"),
|
||||
| 50 => hex(0xfafafa),
|
||||
| 100 => hex(0xf5f5f5),
|
||||
| 200 => hex(0xe5e5e5),
|
||||
| 300 => hex(0xd4d4d4),
|
||||
| 400 => hex(0xa3a3a3),
|
||||
| 500 => hex(0x737373),
|
||||
| 600 => hex(0x525252),
|
||||
| 700 => hex(0x404040),
|
||||
| 800 => hex(0x262626),
|
||||
| 900 => hex(0x171717),
|
||||
| 950 => hex(0x0a0a0a),
|
||||
| _ => panic!("unsupported Tailwind neutral shade"),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const fn violet(shade: u16) -> Rgba {
|
||||
match shade {
|
||||
50 => hex(0xf5f3ff),
|
||||
100 => hex(0xede9fe),
|
||||
200 => hex(0xddd6fe),
|
||||
300 => hex(0xc4b5fd),
|
||||
400 => hex(0xa78bfa),
|
||||
500 => hex(0x8b5cf6),
|
||||
600 => hex(0x7c3aed),
|
||||
700 => hex(0x6d28d9),
|
||||
800 => hex(0x5b21b6),
|
||||
900 => hex(0x4c1d95),
|
||||
950 => hex(0x2e1065),
|
||||
_ => panic!("unsupported Tailwind violet shade"),
|
||||
| 50 => hex(0xf5f3ff),
|
||||
| 100 => hex(0xede9fe),
|
||||
| 200 => hex(0xddd6fe),
|
||||
| 300 => hex(0xc4b5fd),
|
||||
| 400 => hex(0xa78bfa),
|
||||
| 500 => hex(0x8b5cf6),
|
||||
| 600 => hex(0x7c3aed),
|
||||
| 700 => hex(0x6d28d9),
|
||||
| 800 => hex(0x5b21b6),
|
||||
| 900 => hex(0x4c1d95),
|
||||
| 950 => hex(0x2e1065),
|
||||
| _ => panic!("unsupported Tailwind violet shade"),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const fn amber(shade: u16) -> Rgba {
|
||||
match shade {
|
||||
50 => hex(0xfffbeb),
|
||||
100 => hex(0xfef3c7),
|
||||
200 => hex(0xfde68a),
|
||||
300 => hex(0xfcd34d),
|
||||
400 => hex(0xfbbf24),
|
||||
500 => hex(0xf59e0b),
|
||||
600 => hex(0xd97706),
|
||||
700 => hex(0xb45309),
|
||||
800 => hex(0x92400e),
|
||||
900 => hex(0x78350f),
|
||||
950 => hex(0x451a03),
|
||||
_ => panic!("unsupported Tailwind amber shade"),
|
||||
| 50 => hex(0xfffbeb),
|
||||
| 100 => hex(0xfef3c7),
|
||||
| 200 => hex(0xfde68a),
|
||||
| 300 => hex(0xfcd34d),
|
||||
| 400 => hex(0xfbbf24),
|
||||
| 500 => hex(0xf59e0b),
|
||||
| 600 => hex(0xd97706),
|
||||
| 700 => hex(0xb45309),
|
||||
| 800 => hex(0x92400e),
|
||||
| 900 => hex(0x78350f),
|
||||
| 950 => hex(0x451a03),
|
||||
| _ => panic!("unsupported Tailwind amber shade"),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const fn red(shade: u16) -> Rgba {
|
||||
match shade {
|
||||
50 => hex(0xfef2f2),
|
||||
100 => hex(0xfee2e2),
|
||||
200 => hex(0xfecaca),
|
||||
300 => hex(0xfca5a5),
|
||||
400 => hex(0xf87171),
|
||||
500 => hex(0xef4444),
|
||||
600 => hex(0xdc2626),
|
||||
700 => hex(0xb91c1c),
|
||||
800 => hex(0x991b1b),
|
||||
900 => hex(0x7f1d1d),
|
||||
950 => hex(0x450a0a),
|
||||
_ => panic!("unsupported Tailwind red shade"),
|
||||
| 50 => hex(0xfef2f2),
|
||||
| 100 => hex(0xfee2e2),
|
||||
| 200 => hex(0xfecaca),
|
||||
| 300 => hex(0xfca5a5),
|
||||
| 400 => hex(0xf87171),
|
||||
| 500 => hex(0xef4444),
|
||||
| 600 => hex(0xdc2626),
|
||||
| 700 => hex(0xb91c1c),
|
||||
| 800 => hex(0x991b1b),
|
||||
| 900 => hex(0x7f1d1d),
|
||||
| 950 => hex(0x450a0a),
|
||||
| _ => panic!("unsupported Tailwind red shade"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,8 +87,8 @@ impl gpui::RenderOnce for Button {
|
||||
let theme = app::current_theme(cx);
|
||||
|
||||
let icon_color = match self.variant {
|
||||
| Variant::Primary => theme.colors.accent_text,
|
||||
| Variant::Secondary => theme.colors.text,
|
||||
| Variant::Primary => theme.colors.accent_on_solid,
|
||||
| Variant::Secondary => theme.colors.text,
|
||||
};
|
||||
|
||||
let mut children: Vec<AnyElement> = Vec::with_capacity(3);
|
||||
@@ -115,10 +115,10 @@ impl gpui::RenderOnce for Button {
|
||||
.py_0p5()
|
||||
.children(children)
|
||||
.when(matches!(self.variant, Variant::Primary), |div| {
|
||||
div.bg(theme.colors.accent)
|
||||
.text_color(theme.colors.accent_text)
|
||||
div.bg(theme.colors.accent_solid)
|
||||
.text_color(theme.colors.accent_on_solid)
|
||||
.border_1()
|
||||
.border_color(theme.colors.border.blend(theme.colors.accent))
|
||||
.border_color(theme.colors.border.blend(theme.colors.accent_solid))
|
||||
})
|
||||
.when(matches!(self.variant, Variant::Secondary), |div| {
|
||||
div.bg(theme.colors.surface_elevated)
|
||||
|
||||
@@ -115,7 +115,7 @@ pub(crate) fn new(
|
||||
impl Styled for ContentBlock {
|
||||
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
||||
match self {
|
||||
| ContentBlock::Text { style, .. } => style,
|
||||
| ContentBlock::Text { style, .. } => style,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,54 +179,56 @@ impl MarkdownText {
|
||||
}
|
||||
|
||||
match node.kind_id() {
|
||||
| MARKDOWN_KIND_ID_EMPHASIS => {
|
||||
highlights.push((
|
||||
node_range!(),
|
||||
gpui::HighlightStyle {
|
||||
font_style: Some(gpui::FontStyle::Italic),
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
}
|
||||
| MARKDOWN_KIND_ID_STRONG_EMPHASIS => highlights.push((
|
||||
node_range!(),
|
||||
gpui::HighlightStyle {
|
||||
font_weight: Some(gpui::FontWeight::BOLD),
|
||||
..Default::default()
|
||||
},
|
||||
)),
|
||||
|
||||
| MARKDOWN_KIND_ID_LINK => {
|
||||
if cursor.goto_first_child() {
|
||||
| MARKDOWN_KIND_ID_EMPHASIS => {
|
||||
highlights.push((
|
||||
node_range!(),
|
||||
gpui::HighlightStyle {
|
||||
color: Some(theme.colors.accent.into()),
|
||||
underline: Some(gpui::UnderlineStyle {
|
||||
color: Some(theme.colors.accent.into()),
|
||||
thickness: px(1.),
|
||||
wavy: false,
|
||||
}),
|
||||
font_style: Some(gpui::FontStyle::Italic),
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
}
|
||||
| MARKDOWN_KIND_ID_STRONG_EMPHASIS => highlights.push((
|
||||
node_range!(),
|
||||
gpui::HighlightStyle {
|
||||
font_weight: Some(gpui::FontWeight::BOLD),
|
||||
..Default::default()
|
||||
},
|
||||
)),
|
||||
|
||||
if cursor.goto_next_sibling()
|
||||
&& let Ok(src) = cursor.node().utf8_text(content.as_bytes())
|
||||
{
|
||||
links
|
||||
.push((node_range!(), gpui::SharedString::from(String::from(src))));
|
||||
} else {
|
||||
// the link src is invalid, use an empty string as a fallback
|
||||
// link on click handler will ignore empty string
|
||||
links.push((node_range!(), "".into()))
|
||||
| MARKDOWN_KIND_ID_LINK => {
|
||||
if cursor.goto_first_child() {
|
||||
highlights.push((
|
||||
node_range!(),
|
||||
gpui::HighlightStyle {
|
||||
color: Some(theme.colors.link.into()),
|
||||
underline: Some(gpui::UnderlineStyle {
|
||||
color: Some(theme.colors.link.into()),
|
||||
thickness: px(1.),
|
||||
wavy: false,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
|
||||
if cursor.goto_next_sibling()
|
||||
&& let Ok(src) = cursor.node().utf8_text(content.as_bytes())
|
||||
{
|
||||
links.push((
|
||||
node_range!(),
|
||||
gpui::SharedString::from(String::from(src)),
|
||||
));
|
||||
} else {
|
||||
// the link src is invalid, use an empty string as a fallback
|
||||
// link on click handler will ignore empty string
|
||||
links.push((node_range!(), "".into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
| _ => {
|
||||
// extend here to support more markdown node stylings
|
||||
}
|
||||
| _ => {
|
||||
// extend here to support more markdown node stylings
|
||||
}
|
||||
};
|
||||
|
||||
if !cursor.goto_next_sibling() {
|
||||
@@ -303,23 +305,23 @@ impl MarkdownText {
|
||||
let marker_content = &content[marker_node.byte_range()];
|
||||
|
||||
let list_marker_char = match marker_content {
|
||||
// unordered list item
|
||||
| "-" | "+" | "*" => Some("•".to_string()),
|
||||
// unordered list item
|
||||
| "-" | "+" | "*" => Some("•".to_string()),
|
||||
|
||||
| marker_content if ORDERED_LIST_MARKER_REGEX.is_match(marker_content) => {
|
||||
let i = list_index.get_or_insert_with(|| {
|
||||
marker_content
|
||||
.strip_suffix('.')
|
||||
.unwrap()
|
||||
.parse::<usize>()
|
||||
.unwrap()
|
||||
});
|
||||
let j = *i;
|
||||
*i = j + 1;
|
||||
Some(format!("{j}."))
|
||||
}
|
||||
| marker_content if ORDERED_LIST_MARKER_REGEX.is_match(marker_content) => {
|
||||
let i = list_index.get_or_insert_with(|| {
|
||||
marker_content
|
||||
.strip_suffix('.')
|
||||
.unwrap()
|
||||
.parse::<usize>()
|
||||
.unwrap()
|
||||
});
|
||||
let j = *i;
|
||||
*i = j + 1;
|
||||
Some(format!("{j}."))
|
||||
}
|
||||
|
||||
| _ => None,
|
||||
| _ => None,
|
||||
};
|
||||
|
||||
let Some(list_marker_char) = list_marker_char else {
|
||||
@@ -331,9 +333,9 @@ impl MarkdownText {
|
||||
let block = if cursor.goto_next_sibling() {
|
||||
let mut b = block_for_node(cursor, content, 0, theme);
|
||||
match b {
|
||||
| ContentBlock::Text {
|
||||
ref mut decoration, ..
|
||||
} => *decoration = Some(list_marker_char.into()),
|
||||
| ContentBlock::Text {
|
||||
ref mut decoration, ..
|
||||
} => *decoration = Some(list_marker_char.into()),
|
||||
}
|
||||
b
|
||||
} else {
|
||||
@@ -372,150 +374,150 @@ impl MarkdownText {
|
||||
}
|
||||
|
||||
match current_node.kind_id() {
|
||||
| MARKDOWN_KIND_ID_ATX_HEADING => {
|
||||
if !cursor.goto_first_child() {
|
||||
render_fallback_content(&cursor, &self.content, &mut self.blocks);
|
||||
continue;
|
||||
}
|
||||
|
||||
let marker_node_kind = cursor.node().kind_id();
|
||||
|
||||
let block = if cursor.goto_next_sibling()
|
||||
&& cursor.node().kind_id() == MARKDOWN_KIND_ID_HEADING_CONTENT
|
||||
{
|
||||
// because HEADING_CONTENT node includes the space after the heading marker
|
||||
// offset by 1 to exclude the space
|
||||
block_for_node(&mut cursor, &self.content, 1, theme)
|
||||
} else {
|
||||
ContentBlock::Text {
|
||||
decoration: None,
|
||||
text: gpui::SharedString::new(&self.content[current_node.byte_range()]),
|
||||
highlights: Vec::new(),
|
||||
links: Vec::new(),
|
||||
style: gpui::StyleRefinement::default(),
|
||||
| MARKDOWN_KIND_ID_ATX_HEADING => {
|
||||
if !cursor.goto_first_child() {
|
||||
render_fallback_content(&cursor, &self.content, &mut self.blocks);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let mut block = match marker_node_kind {
|
||||
| MARKDOWN_KIND_ID_ATX_H1_MARKER => block
|
||||
.text_size(rems(2.25))
|
||||
.font_weight(gpui::FontWeight::EXTRA_BOLD)
|
||||
.mb_6(),
|
||||
| MARKDOWN_KIND_ID_ATX_H2_MARKER => block
|
||||
.text_2xl()
|
||||
.font_weight(gpui::FontWeight::BOLD)
|
||||
.mt_12()
|
||||
.mb_4(),
|
||||
| MARKDOWN_KIND_ID_ATX_H3_MARKER => block
|
||||
.text_xl()
|
||||
.font_weight(gpui::FontWeight::SEMIBOLD)
|
||||
.mt_8()
|
||||
.mb_3(),
|
||||
| MARKDOWN_KIND_ID_ATX_H4_MARKER => block
|
||||
.text_base()
|
||||
.font_weight(gpui::FontWeight::SEMIBOLD)
|
||||
.mt_6()
|
||||
.mb_2(),
|
||||
| _ => block,
|
||||
}
|
||||
.text_color(theme.colors.text);
|
||||
let marker_node_kind = cursor.node().kind_id();
|
||||
|
||||
if is_first_heading {
|
||||
is_first_heading = false;
|
||||
block = block.mt_0();
|
||||
let block = if cursor.goto_next_sibling()
|
||||
&& cursor.node().kind_id() == MARKDOWN_KIND_ID_HEADING_CONTENT
|
||||
{
|
||||
// because HEADING_CONTENT node includes the space after the heading marker
|
||||
// offset by 1 to exclude the space
|
||||
block_for_node(&mut cursor, &self.content, 1, theme)
|
||||
} else {
|
||||
ContentBlock::Text {
|
||||
decoration: None,
|
||||
text: gpui::SharedString::new(&self.content[current_node.byte_range()]),
|
||||
highlights: Vec::new(),
|
||||
links: Vec::new(),
|
||||
style: gpui::StyleRefinement::default(),
|
||||
}
|
||||
};
|
||||
|
||||
let mut block = match marker_node_kind {
|
||||
| MARKDOWN_KIND_ID_ATX_H1_MARKER => block
|
||||
.text_size(rems(2.25))
|
||||
.font_weight(gpui::FontWeight::EXTRA_BOLD)
|
||||
.mb_6(),
|
||||
| MARKDOWN_KIND_ID_ATX_H2_MARKER => block
|
||||
.text_2xl()
|
||||
.font_weight(gpui::FontWeight::BOLD)
|
||||
.mt_12()
|
||||
.mb_4(),
|
||||
| MARKDOWN_KIND_ID_ATX_H3_MARKER => block
|
||||
.text_xl()
|
||||
.font_weight(gpui::FontWeight::SEMIBOLD)
|
||||
.mt_8()
|
||||
.mb_3(),
|
||||
| MARKDOWN_KIND_ID_ATX_H4_MARKER => block
|
||||
.text_base()
|
||||
.font_weight(gpui::FontWeight::SEMIBOLD)
|
||||
.mt_6()
|
||||
.mb_2(),
|
||||
| _ => block,
|
||||
}
|
||||
.text_color(theme.colors.text);
|
||||
|
||||
if is_first_heading {
|
||||
is_first_heading = false;
|
||||
block = block.mt_0();
|
||||
}
|
||||
|
||||
cursor.goto_parent();
|
||||
|
||||
self.blocks.push(block);
|
||||
}
|
||||
|
||||
cursor.goto_parent();
|
||||
| MARKDOWN_KIND_ID_PARAGRAPH => {
|
||||
let block = block_for_node(&mut cursor, &self.content, 0, theme)
|
||||
.text_color(theme.colors.text)
|
||||
.text_sm();
|
||||
|
||||
self.blocks.push(block);
|
||||
}
|
||||
|
||||
| MARKDOWN_KIND_ID_PARAGRAPH => {
|
||||
let block = block_for_node(&mut cursor, &self.content, 0, theme)
|
||||
.text_color(theme.colors.text)
|
||||
.text_sm();
|
||||
|
||||
self.blocks.push(block);
|
||||
}
|
||||
|
||||
| MARKDOWN_KIND_ID_TIGHT_LIST => {
|
||||
let is_rendered =
|
||||
render_list_node(&mut cursor, &self.content, &mut self.blocks, theme, 0);
|
||||
if !is_rendered {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
| MARKDOWN_KIND_ID_FENCED_CODE_BLOCK => {
|
||||
// expected tree shape:
|
||||
// fenced_code_block
|
||||
// ├── info_string? (present if there is a language annotation)
|
||||
// └── code_fence_content? (present if there is some content between the backticks)
|
||||
|
||||
if !cursor.goto_first_child() {
|
||||
render_fallback_content(&cursor, &self.content, &mut self.blocks);
|
||||
continue;
|
||||
self.blocks.push(block);
|
||||
}
|
||||
|
||||
let content = if cursor.node().kind_id() == MARKDOWN_KIND_ID_INFO_STRING {
|
||||
// skipping info string (which annotates the code block)
|
||||
if cursor.goto_next_sibling() {
|
||||
// this is code_fence_content node
|
||||
| MARKDOWN_KIND_ID_TIGHT_LIST => {
|
||||
let is_rendered =
|
||||
render_list_node(&mut cursor, &self.content, &mut self.blocks, theme, 0);
|
||||
if !is_rendered {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
| MARKDOWN_KIND_ID_FENCED_CODE_BLOCK => {
|
||||
// expected tree shape:
|
||||
// fenced_code_block
|
||||
// ├── info_string? (present if there is a language annotation)
|
||||
// └── code_fence_content? (present if there is some content between the backticks)
|
||||
|
||||
if !cursor.goto_first_child() {
|
||||
render_fallback_content(&cursor, &self.content, &mut self.blocks);
|
||||
continue;
|
||||
}
|
||||
|
||||
let content = if cursor.node().kind_id() == MARKDOWN_KIND_ID_INFO_STRING {
|
||||
// skipping info string (which annotates the code block)
|
||||
if cursor.goto_next_sibling() {
|
||||
// this is code_fence_content node
|
||||
gpui::SharedString::new(
|
||||
cursor
|
||||
.node()
|
||||
.utf8_text(self.content.as_bytes())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
} else {
|
||||
gpui::SharedString::default()
|
||||
}
|
||||
} else {
|
||||
// assuming the current node is already code_fence_content
|
||||
gpui::SharedString::new(
|
||||
cursor
|
||||
.node()
|
||||
.utf8_text(self.content.as_bytes())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
} else {
|
||||
gpui::SharedString::default()
|
||||
};
|
||||
|
||||
cursor.goto_parent();
|
||||
|
||||
let block = ContentBlock::Text {
|
||||
decoration: None,
|
||||
text: content,
|
||||
highlights: Vec::new(),
|
||||
links: Vec::new(),
|
||||
style: gpui::StyleRefinement::default(),
|
||||
}
|
||||
} else {
|
||||
// assuming the current node is already code_fence_content
|
||||
gpui::SharedString::new(
|
||||
cursor
|
||||
.node()
|
||||
.utf8_text(self.content.as_bytes())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
};
|
||||
|
||||
cursor.goto_parent();
|
||||
|
||||
let block = ContentBlock::Text {
|
||||
decoration: None,
|
||||
text: content,
|
||||
highlights: Vec::new(),
|
||||
links: Vec::new(),
|
||||
style: gpui::StyleRefinement::default(),
|
||||
}
|
||||
.text_sm()
|
||||
.text_color(theme.colors.text)
|
||||
.line_height(relative(1.2))
|
||||
.font_family("Menlo")
|
||||
.px_3()
|
||||
.py_2()
|
||||
.rounded_sm()
|
||||
.bg(theme.colors.surface)
|
||||
.border_1()
|
||||
.my_4()
|
||||
.border_color(theme.colors.border);
|
||||
|
||||
self.blocks.push(block);
|
||||
}
|
||||
|
||||
| _ => {
|
||||
println!(
|
||||
"[WARN] formatting not implemenetd for node type {:?}",
|
||||
current_node.kind()
|
||||
);
|
||||
|
||||
let block = block_for_node(&mut cursor, &self.content, 0, theme)
|
||||
.text_sm()
|
||||
.text_color(theme.colors.text)
|
||||
.text_sm();
|
||||
.line_height(relative(1.2))
|
||||
.font_family("Menlo")
|
||||
.px_3()
|
||||
.py_2()
|
||||
.rounded_sm()
|
||||
.bg(theme.colors.code_bg)
|
||||
.border_1()
|
||||
.my_4()
|
||||
.border_color(theme.colors.code_border);
|
||||
|
||||
self.blocks.push(block);
|
||||
}
|
||||
self.blocks.push(block);
|
||||
}
|
||||
|
||||
| _ => {
|
||||
println!(
|
||||
"[WARN] formatting not implemenetd for node type {:?}",
|
||||
current_node.kind()
|
||||
);
|
||||
|
||||
let block = block_for_node(&mut cursor, &self.content, 0, theme)
|
||||
.text_color(theme.colors.text)
|
||||
.text_sm();
|
||||
|
||||
self.blocks.push(block);
|
||||
}
|
||||
}
|
||||
|
||||
if !cursor.goto_next_sibling() {
|
||||
@@ -533,55 +535,55 @@ impl gpui::Render for MarkdownText {
|
||||
) -> impl gpui::prelude::IntoElement {
|
||||
let children = self.blocks.iter().enumerate().map(|(i, block)| {
|
||||
match block {
|
||||
| ContentBlock::Text {
|
||||
decoration,
|
||||
text,
|
||||
highlights,
|
||||
links,
|
||||
style,
|
||||
} => {
|
||||
let styled_text =
|
||||
gpui::StyledText::new(text.clone()).with_highlights(highlights.clone());
|
||||
| ContentBlock::Text {
|
||||
decoration,
|
||||
text,
|
||||
highlights,
|
||||
links,
|
||||
style,
|
||||
} => {
|
||||
let styled_text =
|
||||
gpui::StyledText::new(text.clone()).with_highlights(highlights.clone());
|
||||
|
||||
let content = if links.is_empty() {
|
||||
div().w_full().child(styled_text)
|
||||
} else {
|
||||
// if link in block, interactive text is needed
|
||||
// to handle link clicks
|
||||
let (link_ranges, srcs): (Vec<_>, Vec<_>) = links.iter().cloned().unzip();
|
||||
let content = if links.is_empty() {
|
||||
div().w_full().child(styled_text)
|
||||
} else {
|
||||
// if link in block, interactive text is needed
|
||||
// to handle link clicks
|
||||
let (link_ranges, srcs): (Vec<_>, Vec<_>) = links.iter().cloned().unzip();
|
||||
|
||||
let weak = cx.entity();
|
||||
let t = gpui::InteractiveText::new(i, styled_text).on_click(
|
||||
link_ranges,
|
||||
move |i, _, cx| {
|
||||
if let Some(src) = srcs.get(i) {
|
||||
weak.update(cx, |this, cx| {
|
||||
this.on_open_link(src, cx);
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
},
|
||||
);
|
||||
let weak = cx.entity();
|
||||
let t = gpui::InteractiveText::new(i, styled_text).on_click(
|
||||
link_ranges,
|
||||
move |i, _, cx| {
|
||||
if let Some(src) = srcs.get(i) {
|
||||
weak.update(cx, |this, cx| {
|
||||
this.on_open_link(src, cx);
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
div().w_full().child(t)
|
||||
};
|
||||
div().w_full().child(t)
|
||||
};
|
||||
|
||||
let mut div = match decoration {
|
||||
| Some(d) => div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.gap_2()
|
||||
.items_start()
|
||||
.child(d.clone())
|
||||
.child(div().flex_1().min_w_0().child(content)),
|
||||
| None => div().w_full().child(content),
|
||||
};
|
||||
let mut div = match decoration {
|
||||
| Some(d) => div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.gap_2()
|
||||
.items_start()
|
||||
.child(d.clone())
|
||||
.child(div().flex_1().min_w_0().child(content)),
|
||||
| None => div().w_full().child(content),
|
||||
};
|
||||
|
||||
div.style().refine(&style);
|
||||
div.style().refine(&style);
|
||||
|
||||
div
|
||||
}
|
||||
div
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
12
src/main.rs
12
src/main.rs
@@ -73,17 +73,17 @@ fn setup_application(cx: &mut gpui::App) {
|
||||
let start = resume_application_state(cx);
|
||||
|
||||
match start {
|
||||
Start::FromScratch => {
|
||||
| Start::FromScratch => {
|
||||
let screen = setup_wizard::new();
|
||||
_ = setup_wizard::open_window(screen, cx);
|
||||
}
|
||||
|
||||
Start::FromSetup(state) => {
|
||||
| Start::FromSetup(state) => {
|
||||
let screen = setup_wizard::from_saved(state);
|
||||
_ = setup_wizard::open_window(screen, cx);
|
||||
}
|
||||
|
||||
Start::FromSaved(_) => {
|
||||
| Start::FromSaved(_) => {
|
||||
_ = dashboard::open_window(cx);
|
||||
}
|
||||
};
|
||||
@@ -123,8 +123,8 @@ fn resume_application_state(cx: &mut gpui::App) -> Start {
|
||||
println!("[main] setup status: {:?}", setup_status);
|
||||
|
||||
match setup_status {
|
||||
setup_wizard::SetupStatus::NotStarted => Start::FromScratch,
|
||||
setup_wizard::SetupStatus::InProgress(state) => Start::FromSetup(state),
|
||||
setup_wizard::SetupStatus::Completed => Start::FromSaved(state),
|
||||
| setup_wizard::SetupStatus::NotStarted => Start::FromScratch,
|
||||
| setup_wizard::SetupStatus::InProgress(state) => Start::FromSetup(state),
|
||||
| setup_wizard::SetupStatus::Completed => Start::FromSaved(state),
|
||||
}
|
||||
}
|
||||
|
||||
18
src/query.rs
18
src/query.rs
@@ -137,10 +137,10 @@ where
|
||||
})?;
|
||||
|
||||
match wait_state {
|
||||
WaitState::Cached => {
|
||||
| WaitState::Cached => {
|
||||
return Ok(ent);
|
||||
}
|
||||
WaitState::Waiting { rx, sub } => {
|
||||
| WaitState::Waiting { rx, sub } => {
|
||||
_ = sub;
|
||||
_ = rx.await;
|
||||
}
|
||||
@@ -181,9 +181,9 @@ where
|
||||
let state = query.raw.read(cx);
|
||||
|
||||
match &state.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()),
|
||||
| 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()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,11 +284,11 @@ where
|
||||
|
||||
entity.raw.update(cx, |state, cx| {
|
||||
state.data = match result {
|
||||
Ok(data) => {
|
||||
| Ok(data) => {
|
||||
println!("[query] OK {}", q.key());
|
||||
QueryData::Some(Box::new(data))
|
||||
}
|
||||
Err(err) => {
|
||||
| Err(err) => {
|
||||
println!("[query] ERR {:?}: {:?}", q.key(), err);
|
||||
QueryData::Err(Box::new(err))
|
||||
}
|
||||
@@ -317,8 +317,8 @@ where
|
||||
.raw
|
||||
.update(cx, |query, cx| {
|
||||
query.data = match result {
|
||||
Ok(data) => QueryData::Some(Box::new(data)),
|
||||
Err(err) => QueryData::Err(Box::new(err)),
|
||||
| Ok(data) => QueryData::Some(Box::new(data)),
|
||||
| Err(err) => QueryData::Err(Box::new(err)),
|
||||
};
|
||||
cx.notify();
|
||||
true
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::{
|
||||
api::{self},
|
||||
app,
|
||||
component::{
|
||||
font_icon::{FontIcon, font_icon},
|
||||
font_icon::{FontIcon, FontIconSvg, font_icon},
|
||||
text::text,
|
||||
},
|
||||
query::{self, QueryStatus, read_query, use_query},
|
||||
@@ -134,6 +134,19 @@ impl gpui::RenderOnce for IssueListItem {
|
||||
fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {
|
||||
let theme = app::current_theme(cx);
|
||||
|
||||
fn pill(label: impl gpui::IntoElement + gpui::Styled, icon: FontIconSvg) -> gpui::Div {
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.justify_start()
|
||||
.rounded_full()
|
||||
.px_2()
|
||||
.gap_1()
|
||||
.child(icon.size_3())
|
||||
.child(label.text_xs())
|
||||
}
|
||||
|
||||
let repo_name_text = match self.repo_name {
|
||||
| Some(name) => text(name),
|
||||
| None => text("Unknown repo"),
|
||||
@@ -141,39 +154,43 @@ impl gpui::RenderOnce for IssueListItem {
|
||||
.text_xs()
|
||||
.opacity(0.5);
|
||||
|
||||
let icon = if self.is_draft {
|
||||
font_icon(FontIcon::PullRequestDraft)
|
||||
.text_color(theme.colors.text)
|
||||
.opacity(0.5)
|
||||
let status_pill = if self.is_draft {
|
||||
pill(
|
||||
text("Draft").text_color(theme.colors.text),
|
||||
font_icon(FontIcon::PullRequestDraft).text_color(theme.colors.text),
|
||||
)
|
||||
.bg(theme.colors.surface)
|
||||
} else {
|
||||
match self.status {
|
||||
| api::issues::PullRequestState::Closed => {
|
||||
font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.danger)
|
||||
| api::issues::PullRequestState::Closed => pill(
|
||||
text("Closed").text_color(theme.colors.danger_on_solid),
|
||||
font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.danger_on_solid),
|
||||
)
|
||||
.bg(theme.colors.danger_solid),
|
||||
| api::issues::PullRequestState::Merged => pill(
|
||||
text("Merged").text_color(theme.colors.accent_on_solid),
|
||||
font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.accent_on_solid),
|
||||
)
|
||||
.bg(theme.colors.accent_solid),
|
||||
| _ => pill(
|
||||
text("Open").text_color(theme.colors.success_on_solid),
|
||||
font_icon(FontIcon::PullRequestArrow).text_color(theme.colors.success_on_solid),
|
||||
)
|
||||
.bg(theme.colors.success_solid),
|
||||
}
|
||||
| api::issues::PullRequestState::Merged => {
|
||||
font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.success)
|
||||
}
|
||||
| _ => font_icon(FontIcon::PullRequestArrow).text_color(theme.colors.success),
|
||||
}
|
||||
}
|
||||
.flex_shrink_0()
|
||||
.size_4();
|
||||
|
||||
let description_text = match self.description {
|
||||
| Some(description) => text(description).text_xs(),
|
||||
| None => text("No description provided").opacity(0.5).text_xs(),
|
||||
};
|
||||
|
||||
let pills_row = div().flex().flex_row().gap_1().my_1().child(status_pill);
|
||||
|
||||
div()
|
||||
.relative()
|
||||
.w_full()
|
||||
.px_1p5()
|
||||
.px_3()
|
||||
.py_1()
|
||||
.gap_2()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.child(icon)
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
@@ -191,33 +208,30 @@ impl gpui::RenderOnce for IssueListItem {
|
||||
.min_w_0()
|
||||
.line_clamp(2),
|
||||
)
|
||||
.child(description_text),
|
||||
.child(pills_row),
|
||||
)
|
||||
.when(!self.is_last, |it| {
|
||||
it.border_b_1().border_color(theme.colors.border)
|
||||
})
|
||||
.when(self.is_selected, |it| {
|
||||
it.bg(gpui::Rgba {
|
||||
a: 0.05,
|
||||
..theme.colors.accent
|
||||
})
|
||||
.overflow_hidden()
|
||||
.border_r_1()
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.right_0()
|
||||
.top_0()
|
||||
.bottom_0()
|
||||
.w_px()
|
||||
.bg(theme.colors.accent)
|
||||
.shadow(vec![gpui::BoxShadow {
|
||||
blur_radius: px(16.),
|
||||
spread_radius: px(2.),
|
||||
color: gpui::Hsla::from(theme.colors.accent).alpha(0.8),
|
||||
offset: point(px(-2.), px(0.)),
|
||||
}]),
|
||||
)
|
||||
it.bg(theme.colors.selection_bg)
|
||||
.overflow_hidden()
|
||||
.border_r_1()
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.right_0()
|
||||
.top_0()
|
||||
.bottom_0()
|
||||
.w_px()
|
||||
.bg(theme.colors.selection_border)
|
||||
.shadow(vec![gpui::BoxShadow {
|
||||
blur_radius: px(16.),
|
||||
spread_radius: px(2.),
|
||||
color: gpui::Hsla::from(theme.colors.selection_border).alpha(0.8),
|
||||
offset: point(px(-2.), px(0.)),
|
||||
}]),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use gpui::{
|
||||
AppContext, InteractiveElement, IntoElement, ParentElement, StatefulInteractiveElement, Styled,
|
||||
div, img, point, prelude::FluentBuilder, px,
|
||||
div, img, prelude::FluentBuilder,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -24,7 +24,7 @@ pub(crate) struct PullRequestView {
|
||||
#[derive(gpui::IntoElement)]
|
||||
struct Toolbar {}
|
||||
|
||||
pub fn new(cx: &mut gpui::Context<PullRequestView>) -> PullRequestView {
|
||||
pub fn new(_cx: &mut gpui::Context<PullRequestView>) -> PullRequestView {
|
||||
PullRequestView {
|
||||
markdown_viewer: None,
|
||||
pull_request_query: None,
|
||||
@@ -89,37 +89,41 @@ impl PullRequestView {
|
||||
.rounded_full();
|
||||
|
||||
match pr.state {
|
||||
| api::issues::PullRequestState::Open => {
|
||||
status_pill = status_pill
|
||||
.bg(theme.colors.success)
|
||||
.child(
|
||||
font_icon(FontIcon::PullRequestArrow)
|
||||
.size_3()
|
||||
.text_color(theme.colors.accent_text),
|
||||
)
|
||||
.child(text("Open").text_color(theme.colors.accent_text).text_xs());
|
||||
}
|
||||
| api::issues::PullRequestState::Closed => {
|
||||
status_pill = status_pill
|
||||
.bg(theme.colors.danger)
|
||||
.child(
|
||||
font_icon(FontIcon::PullRequestClosed)
|
||||
.size_3()
|
||||
.text_color(theme.colors.accent_text),
|
||||
)
|
||||
.child(
|
||||
text("Closed")
|
||||
.text_color(theme.colors.accent_text)
|
||||
| api::issues::PullRequestState::Open => {
|
||||
status_pill = status_pill
|
||||
.bg(theme.colors.success_solid)
|
||||
.child(
|
||||
font_icon(FontIcon::PullRequestArrow)
|
||||
.size_3()
|
||||
.text_color(theme.colors.success_on_solid),
|
||||
)
|
||||
.child(
|
||||
text("Open")
|
||||
.text_color(theme.colors.success_on_solid)
|
||||
.text_xs(),
|
||||
);
|
||||
}
|
||||
| api::issues::PullRequestState::Closed => {
|
||||
status_pill = status_pill
|
||||
.bg(theme.colors.danger_solid)
|
||||
.child(
|
||||
font_icon(FontIcon::PullRequestClosed)
|
||||
.size_3()
|
||||
.text_color(theme.colors.danger_on_solid),
|
||||
)
|
||||
.child(
|
||||
text("Closed")
|
||||
.text_color(theme.colors.danger_on_solid)
|
||||
.text_xs(),
|
||||
);
|
||||
}
|
||||
| api::issues::PullRequestState::Merged => {
|
||||
status_pill = status_pill.bg(theme.colors.accent_solid).child(
|
||||
text("Merged")
|
||||
.text_color(theme.colors.accent_on_solid)
|
||||
.text_xs(),
|
||||
);
|
||||
}
|
||||
| api::issues::PullRequestState::Merged => {
|
||||
status_pill = status_pill.bg(theme.colors.accent).child(
|
||||
text("Merged")
|
||||
.text_color(theme.colors.accent_text)
|
||||
.text_xs(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let merge_text = match (
|
||||
@@ -127,48 +131,48 @@ impl PullRequestView {
|
||||
pr.base_branch_name.as_ref(),
|
||||
pr.head_branch_name.as_ref(),
|
||||
) {
|
||||
| (Some(author), Some(base_branch), Some(head_branch)) => {
|
||||
let str = format!(
|
||||
"{} requested to merge {} into {}",
|
||||
author.login, head_branch, base_branch
|
||||
);
|
||||
| (Some(author), Some(base_branch), Some(head_branch)) => {
|
||||
let str = format!(
|
||||
"{} requested to merge {} into {}",
|
||||
author.login, head_branch, base_branch
|
||||
);
|
||||
|
||||
let head_branch_text_offset = author.login.len() + 20;
|
||||
let base_branch_text_offset = head_branch_text_offset + head_branch.len() + 6;
|
||||
let head_branch_text_offset = author.login.len() + 20;
|
||||
let base_branch_text_offset = head_branch_text_offset + head_branch.len() + 6;
|
||||
|
||||
let highlights = [
|
||||
(
|
||||
0..author.login.len(),
|
||||
gpui::HighlightStyle {
|
||||
font_weight: Some(gpui::FontWeight::BOLD),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
head_branch_text_offset..head_branch_text_offset + head_branch.len(),
|
||||
gpui::HighlightStyle {
|
||||
font_weight: Some(gpui::FontWeight::BOLD),
|
||||
color: Some(theme.colors.accent.into()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
base_branch_text_offset..base_branch_text_offset + base_branch.len(),
|
||||
gpui::HighlightStyle {
|
||||
font_weight: Some(gpui::FontWeight::BOLD),
|
||||
color: Some(theme.colors.accent.into()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
];
|
||||
let highlights = [
|
||||
(
|
||||
0..author.login.len(),
|
||||
gpui::HighlightStyle {
|
||||
font_weight: Some(gpui::FontWeight::BOLD),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
head_branch_text_offset..head_branch_text_offset + head_branch.len(),
|
||||
gpui::HighlightStyle {
|
||||
font_weight: Some(gpui::FontWeight::BOLD),
|
||||
color: Some(theme.colors.accent_fg.into()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
base_branch_text_offset..base_branch_text_offset + base_branch.len(),
|
||||
gpui::HighlightStyle {
|
||||
font_weight: Some(gpui::FontWeight::BOLD),
|
||||
color: Some(theme.colors.accent_fg.into()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
Some((
|
||||
author,
|
||||
gpui::StyledText::new(str).with_highlights(highlights),
|
||||
))
|
||||
}
|
||||
Some((
|
||||
author,
|
||||
gpui::StyledText::new(str).with_highlights(highlights),
|
||||
))
|
||||
}
|
||||
|
||||
| _ => None,
|
||||
| _ => None,
|
||||
};
|
||||
|
||||
let metadata_line =
|
||||
@@ -257,24 +261,24 @@ impl gpui::Render for PullRequestView {
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
div().size_full().child(match &self.pull_request_query {
|
||||
| Some(q) => match read_query(q, cx) {
|
||||
| QueryStatus::Loaded(pr) => self.pr_content(pr, cx),
|
||||
| QueryStatus::Err(e) => div()
|
||||
.size_full()
|
||||
.child(format!("{:?}", e))
|
||||
.into_any_element(),
|
||||
| QueryStatus::Loading => div()
|
||||
.size_full()
|
||||
.child("loading pr content")
|
||||
.into_any_element(),
|
||||
},
|
||||
| None => div().size_full().child("no pr selected").into_any_element(),
|
||||
| Some(q) => match read_query(q, cx) {
|
||||
| QueryStatus::Loaded(pr) => self.pr_content(pr, cx),
|
||||
| QueryStatus::Err(e) => div()
|
||||
.size_full()
|
||||
.child(format!("{:?}", e))
|
||||
.into_any_element(),
|
||||
| QueryStatus::Loading => div()
|
||||
.size_full()
|
||||
.child("loading pr content")
|
||||
.into_any_element(),
|
||||
},
|
||||
| None => div().size_full().child("no pr selected").into_any_element(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl gpui::RenderOnce for Toolbar {
|
||||
fn render(self, window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {
|
||||
fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {
|
||||
fn toolbar_button(id: impl Into<gpui::ElementId>) -> Button {
|
||||
button(id)
|
||||
.px_2p5()
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::{
|
||||
screen::dashboard::{
|
||||
issue_list::{self, IssueList},
|
||||
pull_request_view::{self, PullRequestView},
|
||||
sidebar::{self, Sidebar, SidebarItemValue},
|
||||
titlebar::{self, TitleBar},
|
||||
},
|
||||
};
|
||||
@@ -13,7 +12,6 @@ use crate::{
|
||||
pub(crate) struct Screen {
|
||||
titlebar: gpui::Entity<TitleBar>,
|
||||
issue_list: gpui::Entity<IssueList>,
|
||||
sidebar: gpui::Entity<Sidebar>,
|
||||
pull_request_view: gpui::Entity<PullRequestView>,
|
||||
|
||||
issue_filter: Option<&'static str>,
|
||||
@@ -23,7 +21,6 @@ pub(crate) fn new(cx: &mut gpui::Context<Screen>) -> Screen {
|
||||
let mut screen = Screen {
|
||||
titlebar: cx.new(titlebar::new),
|
||||
issue_list: cx.new(issue_list::new),
|
||||
sidebar: cx.new(|_| sidebar::new()),
|
||||
pull_request_view: cx.new(pull_request_view::new),
|
||||
|
||||
issue_filter: None,
|
||||
@@ -34,35 +31,15 @@ pub(crate) fn new(cx: &mut gpui::Context<Screen>) -> Screen {
|
||||
|
||||
impl Screen {
|
||||
fn on_create(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
let on_item_change = cx.listener(|this, value, _, cx| {
|
||||
this.handle_sidebar_item_change(value, cx);
|
||||
});
|
||||
self.sidebar.update(cx, |sidebar, _| {
|
||||
sidebar.on_item_change(on_item_change);
|
||||
});
|
||||
|
||||
_ = cx
|
||||
.subscribe(&self.issue_list, |this, _, event, cx| match event {
|
||||
| issue_list::Event::ItemSelected(pr_id) => {
|
||||
this.handle_issue_list_item_selected(pr_id, cx);
|
||||
}
|
||||
| issue_list::Event::ItemSelected(pr_id) => {
|
||||
this.handle_issue_list_item_selected(pr_id, cx);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn handle_sidebar_item_change(
|
||||
&mut self,
|
||||
value: &SidebarItemValue,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) {
|
||||
match value {
|
||||
| SidebarItemValue::PullRequest { filter } => {
|
||||
self.issue_filter = Some(*filter);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_issue_list_item_selected(
|
||||
&mut self,
|
||||
id: &api::issues::Id,
|
||||
@@ -97,21 +74,12 @@ impl gpui::Render for Screen {
|
||||
.flex_1()
|
||||
.min_h_0()
|
||||
.w_full()
|
||||
.child(
|
||||
div()
|
||||
.w_40()
|
||||
.flex_shrink_0()
|
||||
.h_full()
|
||||
.child(self.sidebar.clone()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_64()
|
||||
.flex_shrink_0()
|
||||
.h_full()
|
||||
.bg(theme.colors.surface)
|
||||
.border_x_1()
|
||||
.border_color(theme.colors.border)
|
||||
.bg(theme.colors.surface_chrome)
|
||||
.overflow_hidden()
|
||||
.child(self.issue_list.clone()),
|
||||
)
|
||||
|
||||
@@ -160,19 +160,19 @@ impl gpui::RenderOnce for SidebarItem {
|
||||
.px_2()
|
||||
.py_1()
|
||||
.gap_2()
|
||||
.child(font_icon(self.icon).size_3().when(self.is_selected, |it| {
|
||||
it.text_color(theme.colors.accent_text)
|
||||
}))
|
||||
.child(
|
||||
font_icon(self.icon)
|
||||
.size_3()
|
||||
.when(self.is_selected, |it| it.text_color(theme.colors.accent_fg)),
|
||||
)
|
||||
.child(
|
||||
text(self.title)
|
||||
.text_sm()
|
||||
.leading_tight()
|
||||
.when(self.is_selected, |it| {
|
||||
it.text_color(theme.colors.accent_text)
|
||||
}),
|
||||
.when(self.is_selected, |it| it.text_color(theme.colors.accent_fg)),
|
||||
),
|
||||
)
|
||||
.when_some(self.on_click, |it, f| it.on_click(f))
|
||||
.when(self.is_selected, |it| it.bg(theme.colors.accent))
|
||||
.when(self.is_selected, |it| it.bg(theme.colors.selection_bg))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use gpui::{ParentElement, Styled, TitlebarOptions, div};
|
||||
use gpui::{ParentElement, Styled, div};
|
||||
|
||||
use crate::component::button::button;
|
||||
use crate::query::{self, QueryStatus, read_query, use_lazy_query, use_query};
|
||||
use crate::query::{self, QueryStatus, read_query, use_lazy_query};
|
||||
use crate::{
|
||||
api, app,
|
||||
component::{
|
||||
@@ -32,13 +32,13 @@ impl gpui::Render for TitleBar {
|
||||
let user = read_query(&self.fetch_user_query, cx);
|
||||
|
||||
let user_avatar = match user {
|
||||
QueryStatus::Err(api::Error::Unauthenticated) => div().absolute().right_2p5().child(
|
||||
| QueryStatus::Err(api::Error::Unauthenticated) => div().absolute().right_2p5().child(
|
||||
button("login-btn")
|
||||
.leading(font_icon(FontIcon::Github))
|
||||
.label("Login"),
|
||||
),
|
||||
|
||||
_ => div(),
|
||||
| _ => div(),
|
||||
};
|
||||
|
||||
div()
|
||||
@@ -50,7 +50,7 @@ impl gpui::Render for TitleBar {
|
||||
.flex()
|
||||
.px(g.safe_area.size.width)
|
||||
.py_2()
|
||||
.bg(g.current_theme.colors.background)
|
||||
.bg(g.current_theme.colors.surface_chrome)
|
||||
.text_color(g.current_theme.colors.text)
|
||||
.relative()
|
||||
.border_b_1()
|
||||
@@ -61,7 +61,7 @@ impl gpui::Render for TitleBar {
|
||||
}
|
||||
|
||||
impl RepoSelector {
|
||||
pub fn new(cx: &mut gpui::Context<Self>) -> Self {
|
||||
pub fn new(_cx: &mut gpui::Context<Self>) -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ impl GithubStepView {
|
||||
let poll_interval = u64::from(*interval);
|
||||
|
||||
match read_query(query, cx) {
|
||||
QueryStatus::Loaded(data) => {
|
||||
| QueryStatus::Loaded(data) => {
|
||||
let auth_tokens = api::AuthTokens {
|
||||
access_token: data.access_token.clone(),
|
||||
};
|
||||
@@ -226,7 +226,7 @@ impl GithubStepView {
|
||||
.detach();
|
||||
}
|
||||
|
||||
QueryStatus::Err(api::Error::Github(api::GithubError { error, .. })) => {
|
||||
| QueryStatus::Err(api::Error::Github(api::GithubError { error, .. })) => {
|
||||
if error == "authorization_pending" {
|
||||
cx.spawn(async move |weak, cx| {
|
||||
Timer::after(Duration::from_secs(poll_interval)).await;
|
||||
@@ -242,7 +242,7 @@ impl GithubStepView {
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
| _ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,8 +263,8 @@ impl GithubStepView {
|
||||
let theme = app::current_theme(cx);
|
||||
|
||||
let (displayed_code, copyable_code) = match create_device_code_query {
|
||||
QueryStatus::Loaded(data) => (data.user_code.as_str(), Some(data.user_code.clone())),
|
||||
_ => (self.placeholder_code.as_str(), None),
|
||||
| QueryStatus::Loaded(data) => (data.user_code.as_str(), Some(data.user_code.clone())),
|
||||
| _ => (self.placeholder_code.as_str(), None),
|
||||
};
|
||||
|
||||
let border_color = theme.colors.border.clone();
|
||||
@@ -358,14 +358,14 @@ impl gpui::Render for GithubStepView {
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
let (can_go_next, header, body) = match self.user_query {
|
||||
None => (false, self.header(), self.device_code_area(cx)),
|
||||
Some(ref q) => {
|
||||
| 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) => {
|
||||
| QueryStatus::Loaded(user) => {
|
||||
(true, connected_header(), connected_body(user, cx))
|
||||
}
|
||||
_ => (false, self.header(), self.device_code_area(cx)),
|
||||
| _ => (false, self.header(), self.device_code_area(cx)),
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -436,7 +436,7 @@ fn connected_body(user: &api::user::User, cx: &gpui::Context<GithubStepView>) ->
|
||||
.rounded_2xl()
|
||||
.border_1()
|
||||
.w_full()
|
||||
.border_color(theme.colors.surface_elevated)
|
||||
.border_color(theme.colors.border)
|
||||
.p_4()
|
||||
.child(
|
||||
div()
|
||||
@@ -461,9 +461,13 @@ fn connected_body(user: &api::user::User, cx: &gpui::Context<GithubStepView>) ->
|
||||
.child(
|
||||
div()
|
||||
.rounded_full()
|
||||
.bg(theme.colors.accent)
|
||||
.bg(theme.colors.success_solid)
|
||||
.p_1()
|
||||
.child(font_icon(FontIcon::Check).size_4()),
|
||||
.child(
|
||||
font_icon(FontIcon::Check)
|
||||
.size_4()
|
||||
.text_color(theme.colors.success_on_solid),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -51,9 +51,9 @@ pub fn open_window(screen: Screen, cx: &mut gpui::App) -> anyhow::Result<()> {
|
||||
impl Step {
|
||||
pub const fn order(&self) -> usize {
|
||||
match self {
|
||||
Step::Welcome => 0,
|
||||
Step::ConnectToGithub => 1,
|
||||
Step::SetupComplete => 2,
|
||||
| Step::Welcome => 0,
|
||||
| Step::ConnectToGithub => 1,
|
||||
| Step::SetupComplete => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,9 +38,9 @@ pub(crate) fn from_saved(state: StoredSetupState) -> Screen {
|
||||
impl Screen {
|
||||
fn advance_to_next_step(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
let next_step = match self.current_step {
|
||||
Step::Welcome => Step::ConnectToGithub,
|
||||
Step::ConnectToGithub => Step::SetupComplete,
|
||||
_ => panic!(),
|
||||
| Step::Welcome => Step::ConnectToGithub,
|
||||
| Step::ConnectToGithub => Step::SetupComplete,
|
||||
| _ => panic!(),
|
||||
};
|
||||
self.current_step = next_step;
|
||||
cx.notify();
|
||||
@@ -67,8 +67,8 @@ impl Screen {
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> &gpui::Entity<github_step::GithubStepView> {
|
||||
match self.github_step_view {
|
||||
Some(ref v) => v,
|
||||
None => {
|
||||
| Some(ref v) => v,
|
||||
| None => {
|
||||
let weak = cx.weak_entity();
|
||||
self.github_step_view = Some(cx.new(|cx| {
|
||||
let mut v = github_step::new(cx);
|
||||
@@ -90,9 +90,9 @@ impl Screen {
|
||||
.enumerate()
|
||||
.map(|(i, step)| {
|
||||
let label = match step {
|
||||
Step::Welcome => "Welcome!",
|
||||
Step::ConnectToGithub => "Connect to GitHub",
|
||||
Step::SetupComplete => "Complete!",
|
||||
| Step::Welcome => "Welcome!",
|
||||
| Step::ConnectToGithub => "Connect to GitHub",
|
||||
| Step::SetupComplete => "Complete!",
|
||||
};
|
||||
let is_completed = i < self.current_step.order();
|
||||
let is_current = self.current_step == *step;
|
||||
@@ -138,16 +138,16 @@ impl gpui::Render for Screen {
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
let step_view = match self.current_step {
|
||||
Step::Welcome => welcome_step()
|
||||
| Step::Welcome => welcome_step()
|
||||
.on_next(cx.listener(|this, _, _, cx| this.advance_to_next_step(cx)))
|
||||
.into_any_element(),
|
||||
|
||||
Step::ConnectToGithub => match self.github_step_view {
|
||||
Some(ref view) => view.clone().into_any_element(),
|
||||
None => self.init_github_step_view(cx).clone().into_any_element(),
|
||||
| Step::ConnectToGithub => match self.github_step_view {
|
||||
| Some(ref view) => view.clone().into_any_element(),
|
||||
| None => self.init_github_step_view(cx).clone().into_any_element(),
|
||||
},
|
||||
|
||||
Step::SetupComplete => setup_complete_step().into_any_element(),
|
||||
| Step::SetupComplete => setup_complete_step().into_any_element(),
|
||||
};
|
||||
|
||||
let theme = app::current_theme(cx);
|
||||
@@ -165,7 +165,7 @@ impl gpui::Render for Screen {
|
||||
.justify_center()
|
||||
.w_1_3()
|
||||
.h_full()
|
||||
.bg(theme.colors.surface)
|
||||
.bg(theme.colors.surface_chrome)
|
||||
.relative()
|
||||
.child(
|
||||
div()
|
||||
|
||||
@@ -16,12 +16,12 @@ pub(crate) struct PersistedState {
|
||||
|
||||
pub(crate) fn data_dir_path() -> std::path::PathBuf {
|
||||
match std::env::consts::OS {
|
||||
"macos" => std::env::home_dir()
|
||||
| "macos" => std::env::home_dir()
|
||||
.unwrap()
|
||||
.join("Library")
|
||||
.join("Application Support")
|
||||
.join("novem"),
|
||||
_ => unimplemented!(
|
||||
| _ => unimplemented!(
|
||||
"data_dir_path is unimplemented for OS: {}",
|
||||
std::env::consts::OS
|
||||
),
|
||||
|
||||
114
src/theme.rs
114
src/theme.rs
@@ -1,6 +1,6 @@
|
||||
use gpui::Rgba;
|
||||
mod catppuccin;
|
||||
|
||||
use crate::colors::hex;
|
||||
use gpui::Rgba;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum ThemeMode {
|
||||
@@ -28,15 +28,49 @@ pub struct ThemeColors {
|
||||
pub background: Rgba,
|
||||
pub surface: Rgba,
|
||||
pub surface_elevated: Rgba,
|
||||
pub surface_chrome: Rgba,
|
||||
pub surface_hover: Rgba,
|
||||
pub surface_active: Rgba,
|
||||
pub border: Rgba,
|
||||
pub border_muted: Rgba,
|
||||
pub border_strong: Rgba,
|
||||
pub focus_ring: Rgba,
|
||||
pub text: Rgba,
|
||||
pub text_muted: Rgba,
|
||||
pub accent: Rgba,
|
||||
pub accent_hover: Rgba,
|
||||
pub accent_text: Rgba,
|
||||
pub success: Rgba,
|
||||
pub warning: Rgba,
|
||||
pub danger: Rgba,
|
||||
pub text_subtle: Rgba,
|
||||
pub text_disabled: Rgba,
|
||||
pub icon_muted: Rgba,
|
||||
pub link: Rgba,
|
||||
pub link_hover: Rgba,
|
||||
pub code_bg: Rgba,
|
||||
pub code_border: Rgba,
|
||||
pub selection_bg: Rgba,
|
||||
pub selection_border: Rgba,
|
||||
pub accent_fg: Rgba,
|
||||
pub accent_muted: Rgba,
|
||||
pub accent_border: Rgba,
|
||||
pub accent_solid: Rgba,
|
||||
pub accent_on_solid: Rgba,
|
||||
pub success_fg: Rgba,
|
||||
pub success_muted: Rgba,
|
||||
pub success_border: Rgba,
|
||||
pub success_solid: Rgba,
|
||||
pub success_on_solid: Rgba,
|
||||
pub warning_fg: Rgba,
|
||||
pub warning_muted: Rgba,
|
||||
pub warning_border: Rgba,
|
||||
pub warning_solid: Rgba,
|
||||
pub warning_on_solid: Rgba,
|
||||
pub danger_fg: Rgba,
|
||||
pub danger_muted: Rgba,
|
||||
pub danger_border: Rgba,
|
||||
pub danger_solid: Rgba,
|
||||
pub danger_on_solid: Rgba,
|
||||
pub info_fg: Rgba,
|
||||
pub info_muted: Rgba,
|
||||
pub info_border: Rgba,
|
||||
pub info_solid: Rgba,
|
||||
pub info_on_solid: Rgba,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
@@ -52,20 +86,20 @@ impl ThemeFamily {
|
||||
|
||||
pub const fn id(self) -> &'static str {
|
||||
match self {
|
||||
| Self::Catppuccin => "catppuccin",
|
||||
| Self::Catppuccin => catppuccin::FAMILY_ID,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn label(self) -> &'static str {
|
||||
match self {
|
||||
| Self::Catppuccin => "Catppuccin",
|
||||
| Self::Catppuccin => catppuccin::FAMILY_LABEL,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn variant(self, mode: ThemeMode) -> ThemeVariant {
|
||||
match (self, mode) {
|
||||
| (Self::Catppuccin, ThemeMode::Light) => ThemeVariant::CatppuccinLatte,
|
||||
| (Self::Catppuccin, ThemeMode::Dark) => ThemeVariant::CatppuccinMocha,
|
||||
| (Self::Catppuccin, ThemeMode::Light) => ThemeVariant::CatppuccinLatte,
|
||||
| (Self::Catppuccin, ThemeMode::Dark) => ThemeVariant::CatppuccinMocha,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,64 +126,28 @@ impl ThemeVariant {
|
||||
|
||||
pub const fn family(self) -> ThemeFamily {
|
||||
match self {
|
||||
| Self::CatppuccinLatte | Self::CatppuccinMocha => ThemeFamily::Catppuccin,
|
||||
| Self::CatppuccinLatte | Self::CatppuccinMocha => ThemeFamily::Catppuccin,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn mode(self) -> ThemeMode {
|
||||
match self {
|
||||
| Self::CatppuccinLatte => ThemeMode::Light,
|
||||
| Self::CatppuccinMocha => ThemeMode::Dark,
|
||||
| Self::CatppuccinLatte => ThemeMode::Light,
|
||||
| Self::CatppuccinMocha => ThemeMode::Dark,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn label(self) -> &'static str {
|
||||
match self {
|
||||
| Self::CatppuccinLatte => "Catppuccin Latte",
|
||||
| Self::CatppuccinMocha => "Catppuccin Mocha",
|
||||
| Self::CatppuccinLatte => catppuccin::LATTE_LABEL,
|
||||
| Self::CatppuccinMocha => catppuccin::MOCHA_LABEL,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn theme(self) -> Theme {
|
||||
match self {
|
||||
| Self::CatppuccinLatte => Theme {
|
||||
id: "catppuccin-latte",
|
||||
name: "Catppuccin Latte",
|
||||
mode: ThemeMode::Light,
|
||||
colors: ThemeColors {
|
||||
background: hex(0xeff1f5),
|
||||
surface: hex(0xeff1f5),
|
||||
surface_elevated: hex(0xdce0e8),
|
||||
border: hex(0xccd0da),
|
||||
text: hex(0x4c4f69),
|
||||
text_muted: hex(0x6c6f85),
|
||||
accent: hex(0x8839ef),
|
||||
accent_hover: hex(0x7287fd),
|
||||
accent_text: hex(0xeff1f5),
|
||||
success: hex(0x40a02b),
|
||||
warning: hex(0xdf8e1d),
|
||||
danger: hex(0xd20f39),
|
||||
},
|
||||
},
|
||||
| Self::CatppuccinMocha => Theme {
|
||||
id: "catppuccin-mocha",
|
||||
name: "Catppuccin Mocha",
|
||||
mode: ThemeMode::Dark,
|
||||
colors: ThemeColors {
|
||||
background: hex(0x1e1e2e),
|
||||
surface: hex(0x181825),
|
||||
surface_elevated: hex(0x313244),
|
||||
border: hex(0x45475a),
|
||||
text: hex(0xcdd6f4),
|
||||
text_muted: hex(0xa6adc8),
|
||||
accent: hex(0xcba6f7),
|
||||
accent_hover: hex(0xb4befe),
|
||||
accent_text: hex(0x1e1e2e),
|
||||
success: hex(0xa6e3a1),
|
||||
warning: hex(0xf9e2af),
|
||||
danger: hex(0xf38ba8),
|
||||
},
|
||||
},
|
||||
| Self::CatppuccinLatte => catppuccin::latte(),
|
||||
| Self::CatppuccinMocha => catppuccin::mocha(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,8 +155,10 @@ impl ThemeVariant {
|
||||
impl From<gpui::WindowAppearance> for ThemeMode {
|
||||
fn from(value: gpui::WindowAppearance) -> Self {
|
||||
match value {
|
||||
| gpui::WindowAppearance::Light | gpui::WindowAppearance::VibrantLight => ThemeMode::Light,
|
||||
| gpui::WindowAppearance::Dark | gpui::WindowAppearance::VibrantDark => ThemeMode::Dark,
|
||||
| gpui::WindowAppearance::Light | gpui::WindowAppearance::VibrantLight => {
|
||||
ThemeMode::Light
|
||||
}
|
||||
| gpui::WindowAppearance::Dark | gpui::WindowAppearance::VibrantDark => ThemeMode::Dark,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
121
src/theme/catppuccin.rs
Normal file
121
src/theme/catppuccin.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use crate::colors::{hex, hex_alpha};
|
||||
|
||||
use super::{Theme, ThemeColors, ThemeMode};
|
||||
|
||||
pub(crate) const FAMILY_ID: &str = "catppuccin";
|
||||
pub(crate) const FAMILY_LABEL: &str = "Catppuccin";
|
||||
|
||||
pub(crate) const LATTE_LABEL: &str = "Catppuccin Latte";
|
||||
pub(crate) const MOCHA_LABEL: &str = "Catppuccin Mocha";
|
||||
|
||||
pub(crate) const fn latte() -> Theme {
|
||||
Theme {
|
||||
id: "catppuccin-latte",
|
||||
name: LATTE_LABEL,
|
||||
mode: ThemeMode::Light,
|
||||
colors: ThemeColors {
|
||||
background: hex(0xeff1f5),
|
||||
surface: hex(0xeff1f5),
|
||||
surface_elevated: hex(0xdce0e8),
|
||||
surface_chrome: hex(0xe6e9ef),
|
||||
surface_hover: hex(0xe6e9ef),
|
||||
surface_active: hex(0xdce0e8),
|
||||
border: hex(0xbcc0cc),
|
||||
border_muted: hex(0xccd0da),
|
||||
border_strong: hex(0xacb0be),
|
||||
focus_ring: hex(0x7287fd),
|
||||
text: hex(0x4c4f69),
|
||||
text_muted: hex(0x5c5f77),
|
||||
text_subtle: hex(0x6c6f85),
|
||||
text_disabled: hex(0x9ca0b0),
|
||||
icon_muted: hex(0x6c6f85),
|
||||
link: hex(0x1e66f5),
|
||||
link_hover: hex(0x7287fd),
|
||||
code_bg: hex(0xe6e9ef),
|
||||
code_border: hex(0xccd0da),
|
||||
selection_bg: hex_alpha(0x8839ef, 0.10),
|
||||
selection_border: hex_alpha(0x8839ef, 0.35),
|
||||
accent_fg: hex(0x8839ef),
|
||||
accent_muted: hex_alpha(0x8839ef, 0.12),
|
||||
accent_border: hex_alpha(0x8839ef, 0.28),
|
||||
accent_solid: hex(0x8839ef),
|
||||
accent_on_solid: hex(0xeff1f5),
|
||||
success_fg: hex(0x40a02b),
|
||||
success_muted: hex_alpha(0x40a02b, 0.12),
|
||||
success_border: hex_alpha(0x40a02b, 0.28),
|
||||
success_solid: hex(0x40a02b),
|
||||
success_on_solid: hex(0x1e1e2e),
|
||||
warning_fg: hex(0xdf8e1d),
|
||||
warning_muted: hex_alpha(0xdf8e1d, 0.12),
|
||||
warning_border: hex_alpha(0xdf8e1d, 0.32),
|
||||
warning_solid: hex(0xdf8e1d),
|
||||
warning_on_solid: hex(0x1e1e2e),
|
||||
danger_fg: hex(0xd20f39),
|
||||
danger_muted: hex_alpha(0xd20f39, 0.12),
|
||||
danger_border: hex_alpha(0xd20f39, 0.28),
|
||||
danger_solid: hex(0xd20f39),
|
||||
danger_on_solid: hex(0xeff1f5),
|
||||
info_fg: hex(0x1e66f5),
|
||||
info_muted: hex_alpha(0x1e66f5, 0.12),
|
||||
info_border: hex_alpha(0x1e66f5, 0.28),
|
||||
info_solid: hex(0x1e66f5),
|
||||
info_on_solid: hex(0xeff1f5),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn mocha() -> Theme {
|
||||
Theme {
|
||||
id: "catppuccin-mocha",
|
||||
name: MOCHA_LABEL,
|
||||
mode: ThemeMode::Dark,
|
||||
colors: ThemeColors {
|
||||
background: hex(0x1e1e2e),
|
||||
surface: hex(0x181825),
|
||||
surface_elevated: hex(0x313244),
|
||||
surface_chrome: hex(0x11111b),
|
||||
surface_hover: hex(0x313244),
|
||||
surface_active: hex(0x45475a),
|
||||
border: hex(0x585b70),
|
||||
border_muted: hex(0x45475a),
|
||||
border_strong: hex(0x6c7086),
|
||||
focus_ring: hex(0xb4befe),
|
||||
text: hex(0xcdd6f4),
|
||||
text_muted: hex(0xbac2de),
|
||||
text_subtle: hex(0xa6adc8),
|
||||
text_disabled: hex(0x7f849c),
|
||||
icon_muted: hex(0xa6adc8),
|
||||
link: hex(0x89b4fa),
|
||||
link_hover: hex(0xb4befe),
|
||||
code_bg: hex(0x11111b),
|
||||
code_border: hex(0x45475a),
|
||||
selection_bg: hex_alpha(0xcba6f7, 0.18),
|
||||
selection_border: hex_alpha(0xcba6f7, 0.45),
|
||||
accent_fg: hex(0xcba6f7),
|
||||
accent_muted: hex_alpha(0xcba6f7, 0.18),
|
||||
accent_border: hex_alpha(0xcba6f7, 0.34),
|
||||
accent_solid: hex(0xcba6f7),
|
||||
accent_on_solid: hex(0x1e1e2e),
|
||||
success_fg: hex(0xa6e3a1),
|
||||
success_muted: hex_alpha(0xa6e3a1, 0.18),
|
||||
success_border: hex_alpha(0xa6e3a1, 0.34),
|
||||
success_solid: hex(0xa6e3a1),
|
||||
success_on_solid: hex(0x1e1e2e),
|
||||
warning_fg: hex(0xf9e2af),
|
||||
warning_muted: hex_alpha(0xf9e2af, 0.18),
|
||||
warning_border: hex_alpha(0xf9e2af, 0.38),
|
||||
warning_solid: hex(0xf9e2af),
|
||||
warning_on_solid: hex(0x1e1e2e),
|
||||
danger_fg: hex(0xf38ba8),
|
||||
danger_muted: hex_alpha(0xf38ba8, 0.18),
|
||||
danger_border: hex_alpha(0xf38ba8, 0.34),
|
||||
danger_solid: hex(0xf38ba8),
|
||||
danger_on_solid: hex(0x1e1e2e),
|
||||
info_fg: hex(0x89b4fa),
|
||||
info_muted: hex_alpha(0x89b4fa, 0.18),
|
||||
info_border: hex_alpha(0x89b4fa, 0.34),
|
||||
info_solid: hex(0x89b4fa),
|
||||
info_on_solid: hex(0x1e1e2e),
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user