Add about internal page

This commit is contained in:
2026-05-08 00:29:29 -04:00
parent 5196aff6a3
commit b4aedad593
8 changed files with 341 additions and 3 deletions
+3
View File
@@ -17,5 +17,8 @@ gpui-component-assets.workspace = true
sha2.workspace = true
thiserror.workspace = true
[build-dependencies]
toml.workspace = true
[lints]
workspace = true
+122
View File
@@ -0,0 +1,122 @@
use std::{
env,
error::Error,
fs,
io::{self, Write},
path::{Path, PathBuf},
process::Command,
};
fn main() -> Result<(), Box<dyn Error>> {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
let workspace_root = workspace_root(&manifest_dir)?;
let workspace_manifest_path = workspace_root.join("Cargo.toml");
let git_head_path = workspace_root.join(".git/HEAD");
let workspace_manifest = read_workspace_manifest(&workspace_manifest_path)?;
let workspace = table(&workspace_manifest, "workspace")?;
let package = table(workspace, "package")?;
let dependencies = table(workspace, "dependencies")?;
emit_cargo_directive(format!("cargo:rerun-if-changed={}", workspace_manifest_path.display()))?;
emit_cargo_directive(format!("cargo:rerun-if-changed={}", git_head_path.display()))?;
if let Some(ref_path) = git_head_ref(&git_head_path)? {
emit_cargo_directive(format!(
"cargo:rerun-if-changed={}",
workspace_root.join(".git").join(ref_path).display()
))?;
}
emit_env("ELY_BUILD_REVISION", &git_revision(workspace_root)?)?;
emit_env("ELY_WORKSPACE_LICENSE", string_value(package, "license")?)?;
emit_env("ELY_GPUI_VERSION", dependency_version(dependencies, "gpui")?)?;
emit_env("ELY_GPUI_COMPONENT_VERSION", dependency_version(dependencies, "gpui-component")?)?;
emit_env("ELY_SERVO_VERSION", dependency_version(dependencies, "servo")?)?;
Ok(())
}
fn workspace_root(manifest_dir: &Path) -> Result<&Path, Box<dyn Error>> {
let crates_dir = manifest_dir
.parent()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing crates directory"))?;
crates_dir
.parent()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing workspace root"))
.map_err(Into::into)
}
fn read_workspace_manifest(path: &Path) -> Result<toml::Table, Box<dyn Error>> {
let manifest = fs::read_to_string(path)?;
manifest.parse::<toml::Table>().map_err(Into::into)
}
fn table<'a>(value: &'a toml::Table, key: &str) -> Result<&'a toml::Table, Box<dyn Error>> {
value
.get(key)
.and_then(toml::Value::as_table)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, format!("missing {key} table")))
.map_err(Into::into)
}
fn string_value<'a>(value: &'a toml::Table, key: &str) -> Result<&'a str, Box<dyn Error>> {
value
.get(key)
.and_then(toml::Value::as_str)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, format!("missing {key} value")))
.map_err(Into::into)
}
fn dependency_version<'a>(
dependencies: &'a toml::Table,
name: &str,
) -> Result<&'a str, Box<dyn Error>> {
let dependency = dependencies.get(name).ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidData, format!("missing {name} dependency"))
})?;
match dependency {
toml::Value::String(version) => Ok(version),
toml::Value::Table(table) => string_value(table, "version"),
_ => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid {name} dependency version"),
)
.into()),
}
}
fn git_revision(workspace_root: &Path) -> Result<String, Box<dyn Error>> {
let output = Command::new("git")
.args(["rev-parse", "--short=12", "HEAD"])
.current_dir(workspace_root)
.output()?;
if !output.status.success() {
return Err(
io::Error::new(io::ErrorKind::InvalidData, "git revision is unavailable").into()
);
}
let revision = String::from_utf8(output.stdout)?.trim().to_string();
if revision.is_empty() {
return Err(io::Error::new(io::ErrorKind::InvalidData, "git revision is empty").into());
}
Ok(revision)
}
fn git_head_ref(git_head_path: &Path) -> Result<Option<String>, Box<dyn Error>> {
let head = fs::read_to_string(git_head_path)?;
let Some(ref_path) = head.trim().strip_prefix("ref: ") else {
return Ok(None);
};
Ok(Some(ref_path.to_string()))
}
fn emit_env(key: &str, value: &str) -> Result<(), Box<dyn Error>> {
if value.trim().is_empty() {
return Err(io::Error::new(io::ErrorKind::InvalidData, format!("{key} is empty")).into());
}
emit_cargo_directive(format!("cargo:rustc-env={key}={value}"))?;
Ok(())
}
fn emit_cargo_directive(directive: impl AsRef<str>) -> Result<(), Box<dyn Error>> {
writeln!(io::stdout(), "{}", directive.as_ref())?;
Ok(())
}
@@ -1,3 +1,4 @@
mod about;
mod download_actions;
mod download_labels;
mod downloads;
@@ -27,6 +28,7 @@ impl ElyShell {
"ely://downloads" => self.render_downloads_page(snapshot, cx),
"ely://history" => self.render_history_page(snapshot, cx),
"ely://archive" => self.render_archive_page(snapshot, cx),
"ely://about" => self.render_about_page(snapshot),
"ely://settings/plugins" => self.render_plugins_page(snapshot, cx),
"ely://settings/profiles" => self.render_profiles_page(snapshot, cx),
"ely://settings/sync" => self.render_sync_page(snapshot),
@@ -0,0 +1,164 @@
use ely_browser_core::BrowserSnapshot;
use ely_design_system::colors;
use gpui::{AnyElement, IntoElement, ParentElement, Styled, div, px, rgb};
use gpui_component::{IconName, StyledExt, scroll::ScrollableElement};
use super::{ElyShell, render_canvas_surface};
const PRODUCT_NAME: &str = "ELY Browser";
const COMPANY_NAME: &str = "Elydora";
const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
const BUILD_REVISION: &str = env!("ELY_BUILD_REVISION");
const WORKSPACE_LICENSE: &str = env!("ELY_WORKSPACE_LICENSE");
const GPUI_VERSION: &str = env!("ELY_GPUI_VERSION");
const GPUI_COMPONENT_VERSION: &str = env!("ELY_GPUI_COMPONENT_VERSION");
const SERVO_VERSION: &str = env!("ELY_SERVO_VERSION");
impl ElyShell {
pub(super) fn render_about_page(&mut self, snapshot: &BrowserSnapshot) -> AnyElement {
render_canvas_surface(
div()
.size_full()
.p_8()
.flex()
.flex_col()
.gap_5()
.child(render_about_header())
.child(render_about_rows(snapshot)),
)
}
}
fn render_about_header() -> AnyElement {
div()
.flex()
.items_end()
.justify_between()
.gap_4()
.child(
div()
.flex()
.flex_col()
.gap_2()
.child(
div()
.text_size(px(26.0))
.text_color(rgb(colors::INK))
.child(format!("About {PRODUCT_NAME}")),
)
.child(div().text_sm().text_color(rgb(colors::MUTED)).child(COMPANY_NAME)),
)
.child(
div()
.flex()
.items_center()
.gap_2()
.text_xs()
.font_semibold()
.text_color(rgb(colors::MUTED))
.child(IconName::Info)
.child(format!("Build {BUILD_REVISION}")),
)
.into_any_element()
}
fn render_about_rows(snapshot: &BrowserSnapshot) -> AnyElement {
div()
.flex_1()
.min_h_0()
.flex()
.flex_col()
.overflow_y_scrollbar()
.border_t_1()
.border_color(rgb(colors::HAIRLINE))
.child(about_row(IconName::Building2, "Product", PRODUCT_NAME, COMPANY_NAME))
.child(about_row(IconName::Info, "Version", APP_VERSION, "Cargo package version"))
.child(about_row(IconName::GitHub, "Build", BUILD_REVISION, "Git revision"))
.child(about_row(
IconName::Frame,
"GPUI",
format!("gpui {GPUI_VERSION}"),
"Native desktop renderer",
))
.child(about_row(
IconName::LayoutDashboard,
"Components",
format!("gpui-component {GPUI_COMPONENT_VERSION}"),
"UI component toolkit",
))
.child(about_row(
IconName::Globe,
"Servo",
format!("servo {SERVO_VERSION}"),
"Browser engine crate",
))
.child(about_row(
IconName::BookOpen,
"License",
WORKSPACE_LICENSE,
"Workspace package license",
))
.child(about_row(
IconName::CircleUser,
"Runtime",
&snapshot.active_profile_name,
format!("{} - {} open tabs", snapshot.active_space_name, snapshot.tabs.len()),
))
.into_any_element()
}
fn about_row(
icon: IconName,
label: &'static str,
value: impl Into<String>,
detail: impl Into<String>,
) -> AnyElement {
let value = value.into();
let detail = detail.into();
div()
.py_3()
.border_b_1()
.border_color(rgb(colors::HAIRLINE))
.flex()
.items_center()
.justify_between()
.gap_4()
.child(
div()
.min_w_0()
.flex_1()
.flex()
.items_center()
.gap_3()
.child(div().text_color(rgb(colors::MUTED_SOFT)).child(icon))
.child(
div()
.min_w_0()
.flex()
.flex_col()
.gap_1()
.child(
div()
.text_sm()
.font_semibold()
.truncate()
.text_color(rgb(colors::INK))
.child(label),
)
.child(
div().text_xs().truncate().text_color(rgb(colors::MUTED)).child(detail),
),
),
)
.child(
div()
.max_w(px(300.0))
.truncate()
.text_sm()
.font_semibold()
.text_color(rgb(colors::INK))
.child(value),
)
.into_any_element()
}