update deps
Some checks failed
Rust / build (macos-latest, stable) (push) Has been cancelled
Rust / build (windows-latest, stable) (push) Has been cancelled
Rust / build (ubuntu-latest, stable) (push) Has been cancelled
Some checks failed
Rust / build (macos-latest, stable) (push) Has been cancelled
Rust / build (windows-latest, stable) (push) Has been cancelled
Rust / build (ubuntu-latest, stable) (push) Has been cancelled
This commit is contained in:
1304
Cargo.lock
generated
1304
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,9 @@ publish = false
|
|||||||
# GPUI
|
# GPUI
|
||||||
gpui = { git = "https://github.com/zed-industries/zed" }
|
gpui = { git = "https://github.com/zed-industries/zed" }
|
||||||
gpui_platform = { git = "https://github.com/zed-industries/zed" }
|
gpui_platform = { git = "https://github.com/zed-industries/zed" }
|
||||||
|
gpui_linux = { git = "https://github.com/zed-industries/zed" }
|
||||||
|
gpui_windows = { git = "https://github.com/zed-industries/zed" }
|
||||||
|
gpui_macos = { git = "https://github.com/zed-industries/zed" }
|
||||||
gpui_tokio = { git = "https://github.com/zed-industries/zed" }
|
gpui_tokio = { git = "https://github.com/zed-industries/zed" }
|
||||||
reqwest_client = { git = "https://github.com/zed-industries/zed" }
|
reqwest_client = { git = "https://github.com/zed-industries/zed" }
|
||||||
|
|
||||||
@@ -29,7 +32,6 @@ futures = "0.3"
|
|||||||
itertools = "0.13.0"
|
itertools = "0.13.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
oneshot = "0.1.10"
|
oneshot = "0.1.10"
|
||||||
reqwest = { version = "0.12", features = ["multipart", "stream", "json"] }
|
|
||||||
flume = { version = "0.11.1", default-features = false, features = ["async", "select"] }
|
flume = { version = "0.11.1", default-features = false, features = ["async", "select"] }
|
||||||
rust-embed = "8.5.0"
|
rust-embed = "8.5.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ common = { path = "../common" }
|
|||||||
|
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
gpui_tokio.workspace = true
|
gpui_tokio.workspace = true
|
||||||
reqwest.workspace = true
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ use anyhow::{anyhow, Context as AnyhowContext, Error};
|
|||||||
use gpui::http_client::{AsyncBody, HttpClient};
|
use gpui::http_client::{AsyncBody, HttpClient};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, Global, Subscription, Task,
|
App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, Global, Subscription, Task,
|
||||||
|
Window,
|
||||||
};
|
};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use smol::fs::File;
|
use smol::fs::File;
|
||||||
|
use smol::io::AsyncReadExt;
|
||||||
use smol::process::Command;
|
use smol::process::Command;
|
||||||
|
|
||||||
const GITHUB_API_URL: &str = "https://api.github.com";
|
const GITHUB_API_URL: &str = "https://api.github.com";
|
||||||
@@ -30,14 +32,14 @@ fn is_flatpak_installation() -> bool {
|
|||||||
std::env::var("FLATPAK_ID").is_ok() || std::env::var(COOP_UPDATE_EXPLANATION).is_ok()
|
std::env::var("FLATPAK_ID").is_ok() || std::env::var(COOP_UPDATE_EXPLANATION).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(window: &mut Window, cx: &mut App) {
|
||||||
// Skip auto-update initialization if installed via Flatpak
|
// Skip auto-update initialization if installed via Flatpak
|
||||||
if is_flatpak_installation() {
|
if is_flatpak_installation() {
|
||||||
log::info!("Skipping auto-update initialization: App is installed via Flatpak");
|
log::info!("Skipping auto-update initialization: App is installed via Flatpak");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AutoUpdater::set_global(cx.new(AutoUpdater::new), cx);
|
AutoUpdater::set_global(cx.new(|cx| AutoUpdater::new(window, cx)), cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GlobalAutoUpdater(Entity<AutoUpdater>);
|
struct GlobalAutoUpdater(Entity<AutoUpdater>);
|
||||||
@@ -181,7 +183,7 @@ pub struct AutoUpdater {
|
|||||||
_subscriptions: SmallVec<[Subscription; 1]>,
|
_subscriptions: SmallVec<[Subscription; 1]>,
|
||||||
|
|
||||||
/// Background tasks
|
/// Background tasks
|
||||||
_tasks: SmallVec<[Task<()>; 2]>,
|
tasks: Vec<Task<Result<(), Error>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AutoUpdater {
|
impl AutoUpdater {
|
||||||
@@ -195,44 +197,9 @@ impl AutoUpdater {
|
|||||||
cx.set_global(GlobalAutoUpdater(state));
|
cx.set_global(GlobalAutoUpdater(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(cx: &mut Context<Self>) -> Self {
|
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
let version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
|
let version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
|
||||||
let async_version = version.clone();
|
|
||||||
|
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
let mut tasks = smallvec![];
|
|
||||||
|
|
||||||
tasks.push(
|
|
||||||
// Check for updates after 2 minutes
|
|
||||||
cx.spawn(async move |this, cx| {
|
|
||||||
cx.background_executor()
|
|
||||||
.timer(Duration::from_secs(120))
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Update the status to checking
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.set_status(AutoUpdateStatus::Checking, cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
match Self::check_for_updates(async_version, cx).await {
|
|
||||||
Ok(download_url) => {
|
|
||||||
// Update the status to checked with download URL
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.set_status(AutoUpdateStatus::checked(download_url), cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::warn!("Failed to check for updates: {e}");
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.set_status(AutoUpdateStatus::Idle, cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
subscriptions.push(
|
subscriptions.push(
|
||||||
// Observe the status
|
// Observe the status
|
||||||
@@ -243,11 +210,16 @@ impl AutoUpdater {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Run at the end of current cycle
|
||||||
|
cx.defer_in(window, |this, _window, cx| {
|
||||||
|
this.check(cx);
|
||||||
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
status: AutoUpdateStatus::Idle,
|
status: AutoUpdateStatus::Idle,
|
||||||
version,
|
version,
|
||||||
|
tasks: vec![],
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
_tasks: tasks,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,31 +228,63 @@ impl AutoUpdater {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_for_updates(version: Version, cx: &AsyncApp) -> Task<Result<String, Error>> {
|
fn check(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let version = self.version.clone();
|
||||||
|
let duration = Duration::from_secs(120);
|
||||||
|
let task = self.check_for_updates(version, cx);
|
||||||
|
|
||||||
|
// Check for updates after 2 minutes
|
||||||
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
|
cx.background_executor().timer(duration).await;
|
||||||
|
|
||||||
|
// Update the status to checking
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.set_status(AutoUpdateStatus::Checking, cx);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
match task.await {
|
||||||
|
Ok(download_url) => {
|
||||||
|
// Update the status to checked with download URL
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.set_status(AutoUpdateStatus::checked(download_url), cx);
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Failed to check for updates: {e}");
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.set_status(AutoUpdateStatus::Idle, cx);
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_for_updates(&self, version: Version, cx: &App) -> Task<Result<String, Error>> {
|
||||||
|
let http_client = cx.http_client();
|
||||||
|
let repo_owner = get_github_repo_owner();
|
||||||
|
let repo_name = get_github_repo_name();
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let client = reqwest::Client::new();
|
|
||||||
let repo_owner = get_github_repo_owner();
|
|
||||||
let repo_name = get_github_repo_name();
|
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}/repos/{}/{}/releases/latest",
|
"{}/repos/{}/{}/releases/latest",
|
||||||
GITHUB_API_URL, repo_owner, repo_name
|
GITHUB_API_URL, repo_owner, repo_name
|
||||||
);
|
);
|
||||||
|
|
||||||
let response = client
|
let async_body = AsyncBody::default();
|
||||||
.get(&url)
|
let mut body = Vec::new();
|
||||||
.header("User-Agent", "Coop-Auto-Updater")
|
let mut response = http_client.get(&url, async_body, false).await?;
|
||||||
.send()
|
|
||||||
.await
|
// Read the response body into a vector
|
||||||
.context("Failed to fetch GitHub releases")?;
|
response.body_mut().read_to_end(&mut body).await?;
|
||||||
|
|
||||||
if !response.status().is_success() {
|
if !response.status().is_success() {
|
||||||
return Err(anyhow!("GitHub API returned error: {}", response.status()));
|
return Err(anyhow!("GitHub API returned error: {}", response.status()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let release: GitHubRelease = response
|
// Parse the response body as JSON
|
||||||
.json()
|
let release: GitHubRelease = serde_json::from_slice(&body)?;
|
||||||
.await
|
|
||||||
.context("Failed to parse GitHub release")?;
|
|
||||||
|
|
||||||
// Parse version from tag (remove 'v' prefix if present)
|
// Parse version from tag (remove 'v' prefix if present)
|
||||||
let tag_version = release.tag_name.trim_start_matches('v');
|
let tag_version = release.tag_name.trim_start_matches('v');
|
||||||
@@ -334,29 +338,31 @@ impl AutoUpdater {
|
|||||||
Ok((installer_dir, target_path))
|
Ok((installer_dir, target_path))
|
||||||
});
|
});
|
||||||
|
|
||||||
self._tasks.push(
|
self.tasks.push(
|
||||||
// Install the new release
|
// Install the new release
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
_ = this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.set_status(AutoUpdateStatus::Installing, cx);
|
this.set_status(AutoUpdateStatus::Installing, cx);
|
||||||
});
|
})?;
|
||||||
|
|
||||||
match task.await {
|
match task.await {
|
||||||
Ok((installer_dir, target_path)) => {
|
Ok((installer_dir, target_path)) => {
|
||||||
if Self::install(installer_dir, target_path, cx).await.is_ok() {
|
if Self::install(installer_dir, target_path, cx).await.is_ok() {
|
||||||
// Update the status to updated
|
// Update the status to updated
|
||||||
_ = this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.set_status(AutoUpdateStatus::Updated, cx);
|
this.set_status(AutoUpdateStatus::Updated, cx);
|
||||||
});
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Update the status to error including the error message
|
// Update the status to error including the error message
|
||||||
_ = this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.set_status(AutoUpdateStatus::error(e.to_string()), cx);
|
this.set_status(AutoUpdateStatus::error(e.to_string()), cx);
|
||||||
});
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::sync::Arc;
|
|||||||
pub use actions::*;
|
pub use actions::*;
|
||||||
use anyhow::{Context as AnyhowContext, Error};
|
use anyhow::{Context as AnyhowContext, Error};
|
||||||
use chat::{Message, RenderedMessage, Room, RoomEvent, SendReport};
|
use chat::{Message, RenderedMessage, Room, RoomEvent, SendReport};
|
||||||
use common::{nip96_upload, RenderedTimestamp};
|
use common::RenderedTimestamp;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
deferred, div, img, list, px, red, relative, rems, svg, white, AnyElement, App, AppContext,
|
deferred, div, img, list, px, red, relative, rems, svg, white, AnyElement, App, AppContext,
|
||||||
@@ -21,7 +21,7 @@ use settings::AppSettings;
|
|||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use smol::fs;
|
use smol::fs;
|
||||||
use smol::lock::RwLock;
|
use smol::lock::RwLock;
|
||||||
use state::NostrRegistry;
|
use state::{nostr_upload, NostrRegistry};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
@@ -504,7 +504,7 @@ impl ChatPanel {
|
|||||||
|
|
||||||
let upload = Tokio::spawn(cx, async move {
|
let upload = Tokio::spawn(cx, async move {
|
||||||
let file = fs::read(path).await.ok()?;
|
let file = fs::read(path).await.ok()?;
|
||||||
let url = nip96_upload(&client, &nip96_server, file).await.ok()?;
|
let url = nostr_upload(&client, &nip96_server, file).await.ok()?;
|
||||||
|
|
||||||
Some(url)
|
Some(url)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ chrono.workspace = true
|
|||||||
smallvec.workspace = true
|
smallvec.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
reqwest.workspace = true
|
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|
||||||
dirs = "5.0"
|
dirs = "5.0"
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
pub use debounced_delay::*;
|
pub use debounced_delay::*;
|
||||||
pub use display::*;
|
pub use display::*;
|
||||||
pub use event::*;
|
pub use event::*;
|
||||||
pub use nip96::*;
|
|
||||||
pub use paths::*;
|
pub use paths::*;
|
||||||
|
|
||||||
mod debounced_delay;
|
mod debounced_delay;
|
||||||
mod display;
|
mod display;
|
||||||
mod event;
|
mod event;
|
||||||
mod nip96;
|
|
||||||
mod paths;
|
mod paths;
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
use anyhow::anyhow;
|
|
||||||
use nostr::prelude::*;
|
|
||||||
use reqwest::Client as ReqClient;
|
|
||||||
|
|
||||||
pub async fn nip05_verify(public_key: PublicKey, address: &str) -> Result<bool, anyhow::Error> {
|
|
||||||
let req_client = ReqClient::new();
|
|
||||||
let address = Nip05Address::parse(address)?;
|
|
||||||
|
|
||||||
// Get NIP-05 response
|
|
||||||
let res = req_client.get(address.url().to_string()).send().await?;
|
|
||||||
let json: Value = res.json().await?;
|
|
||||||
|
|
||||||
let verify = nip05::verify_from_json(&public_key, &address, &json);
|
|
||||||
|
|
||||||
Ok(verify)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn nip05_profile(address: &str) -> Result<Nip05Profile, anyhow::Error> {
|
|
||||||
let req_client = ReqClient::new();
|
|
||||||
let address = Nip05Address::parse(address)?;
|
|
||||||
|
|
||||||
// Get NIP-05 response
|
|
||||||
let res = req_client.get(address.url().to_string()).send().await?;
|
|
||||||
let json: Value = res.json().await?;
|
|
||||||
|
|
||||||
if let Ok(profile) = Nip05Profile::from_json(&address, &json) {
|
|
||||||
Ok(profile)
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("Failed to get NIP-05 profile"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -44,6 +44,9 @@ relay_auth = { path = "../relay_auth" }
|
|||||||
|
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
gpui_platform.workspace = true
|
gpui_platform.workspace = true
|
||||||
|
gpui_linux.workspace = true
|
||||||
|
gpui_windows.workspace = true
|
||||||
|
gpui_macos.workspace = true
|
||||||
gpui_tokio.workspace = true
|
gpui_tokio.workspace = true
|
||||||
reqwest_client.workspace = true
|
reqwest_client.workspace = true
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ fn main() {
|
|||||||
person::init(cx);
|
person::init(cx);
|
||||||
|
|
||||||
// Initialize auto update
|
// Initialize auto update
|
||||||
auto_update::init(cx);
|
auto_update::init(window, cx);
|
||||||
|
|
||||||
// Root Entity
|
// Root Entity
|
||||||
Root::new(workspace::init(window, cx).into(), window, cx)
|
Root::new(workspace::init(window, cx).into(), window, cx)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::str::FromStr;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use common::{nip96_upload, shorten_pubkey};
|
use common::shorten_pubkey;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, rems, AnyElement, App, AppContext, ClipboardItem, Context, Entity, EventEmitter,
|
div, rems, AnyElement, App, AppContext, ClipboardItem, Context, Entity, EventEmitter,
|
||||||
FocusHandle, Focusable, IntoElement, ParentElement, PathPromptOptions, Render, SharedString,
|
FocusHandle, Focusable, IntoElement, ParentElement, PathPromptOptions, Render, SharedString,
|
||||||
@@ -13,7 +13,7 @@ use nostr_sdk::prelude::*;
|
|||||||
use person::{Person, PersonRegistry};
|
use person::{Person, PersonRegistry};
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use smol::fs;
|
use smol::fs;
|
||||||
use state::NostrRegistry;
|
use state::{nostr_upload, NostrRegistry};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
@@ -170,7 +170,7 @@ impl ProfilePanel {
|
|||||||
Ok(Ok(Some(mut paths))) => {
|
Ok(Ok(Some(mut paths))) => {
|
||||||
if let Some(path) = paths.pop() {
|
if let Some(path) = paths.pop() {
|
||||||
let file = fs::read(path).await?;
|
let file = fs::read(path).await?;
|
||||||
let url = nip96_upload(&client, &nip96_server, file).await?;
|
let url = nostr_upload(&client, &nip96_server, file).await?;
|
||||||
|
|
||||||
Ok(url)
|
Ok(url)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ publish.workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
|
|
||||||
|
nostr.workspace = true
|
||||||
nostr-sdk.workspace = true
|
nostr-sdk.workspace = true
|
||||||
nostr-lmdb.workspace = true
|
nostr-lmdb.workspace = true
|
||||||
nostr-connect.workspace = true
|
nostr-connect.workspace = true
|
||||||
@@ -14,7 +15,6 @@ nostr-connect.workspace = true
|
|||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
gpui_tokio.workspace = true
|
gpui_tokio.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
reqwest.workspace = true
|
|
||||||
flume.workspace = true
|
flume.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
@@ -25,3 +25,4 @@ serde_json.workspace = true
|
|||||||
rustls = "0.23"
|
rustls = "0.23"
|
||||||
petname = "2.0.2"
|
petname = "2.0.2"
|
||||||
whoami = "1.6.1"
|
whoami = "1.6.1"
|
||||||
|
reqwest = { version = "0.12", features = ["multipart", "stream", "json"] }
|
||||||
|
|||||||
@@ -13,11 +13,13 @@ use nostr_sdk::prelude::*;
|
|||||||
mod constants;
|
mod constants;
|
||||||
mod gossip;
|
mod gossip;
|
||||||
mod nip05;
|
mod nip05;
|
||||||
|
mod nip96;
|
||||||
mod signer;
|
mod signer;
|
||||||
|
|
||||||
pub use constants::*;
|
pub use constants::*;
|
||||||
pub use gossip::*;
|
pub use gossip::*;
|
||||||
pub use nip05::*;
|
pub use nip05::*;
|
||||||
|
pub use nip96::*;
|
||||||
pub use signer::*;
|
pub use signer::*;
|
||||||
|
|
||||||
pub fn init(window: &mut Window, cx: &mut App) {
|
pub fn init(window: &mut Window, cx: &mut App) {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ where
|
|||||||
Ok(upload_response.download_url()?.to_owned())
|
Ok(upload_response.download_url()?.to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn nip96_upload(
|
pub async fn nostr_upload(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
server: &Url,
|
server: &Url,
|
||||||
file: Vec<u8>,
|
file: Vec<u8>,
|
||||||
@@ -78,6 +78,9 @@ pub struct Theme {
|
|||||||
|
|
||||||
/// Show the scrollbar mode, default: scrolling
|
/// Show the scrollbar mode, default: scrolling
|
||||||
pub scrollbar_mode: ScrollbarMode,
|
pub scrollbar_mode: ScrollbarMode,
|
||||||
|
|
||||||
|
/// Platform kind
|
||||||
|
pub platform: PlatformKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Theme {
|
impl Deref for Theme {
|
||||||
@@ -201,6 +204,7 @@ impl From<ThemeFamily> for Theme {
|
|||||||
mode,
|
mode,
|
||||||
colors: *colors,
|
colors: *colors,
|
||||||
theme: Rc::new(family),
|
theme: Rc::new(family),
|
||||||
|
platform,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
use std::mem;
|
|
||||||
|
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use gpui::MouseButton;
|
use gpui::MouseButton;
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
use gpui::Pixels;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, px, AnyElement, Context, Decorations, Hsla, InteractiveElement as _, IntoElement,
|
div, px, AnyElement, Context, Decorations, Hsla, InteractiveElement as _, IntoElement,
|
||||||
ParentElement, Pixels, Render, StatefulInteractiveElement as _, Styled, Window,
|
ParentElement, Render, StatefulInteractiveElement as _, Styled, Window, WindowControlArea,
|
||||||
WindowControlArea,
|
|
||||||
};
|
};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use theme::{ActiveTheme, PlatformKind, CLIENT_SIDE_DECORATION_ROUNDING};
|
use theme::{ActiveTheme, PlatformKind, CLIENT_SIDE_DECORATION_ROUNDING};
|
||||||
@@ -14,42 +13,39 @@ use ui::h_flex;
|
|||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use crate::platforms::linux::LinuxWindowControls;
|
use crate::platforms::linux::LinuxWindowControls;
|
||||||
|
use crate::platforms::mac::TRAFFIC_LIGHT_PADDING;
|
||||||
use crate::platforms::windows::WindowsWindowControls;
|
use crate::platforms::windows::WindowsWindowControls;
|
||||||
|
|
||||||
mod platforms;
|
mod platforms;
|
||||||
|
|
||||||
|
/// Titlebar
|
||||||
pub struct TitleBar {
|
pub struct TitleBar {
|
||||||
|
/// Children elements of the title bar.
|
||||||
children: SmallVec<[AnyElement; 2]>,
|
children: SmallVec<[AnyElement; 2]>,
|
||||||
platform_kind: PlatformKind,
|
|
||||||
should_move: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for TitleBar {
|
/// Whether the title bar is currently being moved.
|
||||||
fn default() -> Self {
|
should_move: bool,
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TitleBar {
|
impl TitleBar {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
children: smallvec![],
|
children: smallvec![],
|
||||||
platform_kind: PlatformKind::platform(),
|
|
||||||
should_move: false,
|
should_move: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
pub fn height(window: &mut Window) -> Pixels {
|
pub fn height(&self, window: &mut Window) -> Pixels {
|
||||||
(1.75 * window.rem_size()).max(px(34.))
|
(1.75 * window.rem_size()).max(px(34.))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn height(_window: &mut Window) -> Pixels {
|
pub fn height(&self, _window: &mut Window) -> Pixels {
|
||||||
px(32.)
|
px(32.)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn title_bar_color(&self, window: &mut Window, cx: &mut Context<Self>) -> Hsla {
|
pub fn titlebar_color(&self, window: &mut Window, cx: &mut Context<Self>) -> Hsla {
|
||||||
if cfg!(any(target_os = "linux", target_os = "freebsd")) {
|
if cfg!(any(target_os = "linux", target_os = "freebsd")) {
|
||||||
if window.is_window_active() && !self.should_move {
|
if window.is_window_active() && !self.should_move {
|
||||||
cx.theme().title_bar
|
cx.theme().title_bar
|
||||||
@@ -69,66 +65,62 @@ impl TitleBar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParentElement for TitleBar {
|
impl Default for TitleBar {
|
||||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
fn default() -> Self {
|
||||||
self.children.extend(elements)
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for TitleBar {
|
impl Render for TitleBar {
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let height = self.height(window);
|
||||||
|
let color = self.titlebar_color(window, cx);
|
||||||
|
let children = std::mem::take(&mut self.children);
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
let supported_controls = window.window_controls();
|
let supported_controls = window.window_controls();
|
||||||
let decorations = window.window_decorations();
|
let decorations = window.window_decorations();
|
||||||
let height = Self::height(window);
|
|
||||||
let color = self.title_bar_color(window, cx);
|
|
||||||
let children = mem::take(&mut self.children);
|
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.window_control_area(WindowControlArea::Drag)
|
.window_control_area(WindowControlArea::Drag)
|
||||||
.w_full()
|
|
||||||
.h(height)
|
.h(height)
|
||||||
|
.w_full()
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
if window.is_fullscreen() {
|
if window.is_fullscreen() {
|
||||||
this.px_2()
|
this.px_2()
|
||||||
} else if self.platform_kind.is_mac() {
|
} else if cx.theme().platform.is_mac() {
|
||||||
this.pl(px(platforms::mac::TRAFFIC_LIGHT_PADDING))
|
this.pr_2().pl(px(TRAFFIC_LIGHT_PADDING))
|
||||||
.pr_2()
|
|
||||||
.when(children.len() <= 1, |this| {
|
|
||||||
this.pr(px(platforms::mac::TRAFFIC_LIGHT_PADDING))
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
this.px_2()
|
this.px_2()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|this| match decorations {
|
.map(|this| match decorations {
|
||||||
Decorations::Server => this,
|
Decorations::Server => this,
|
||||||
Decorations::Client { tiling, .. } => this
|
Decorations::Client { tiling } => this
|
||||||
.when(!(tiling.top || tiling.right), |el| {
|
.when(!(tiling.top || tiling.right), |div| {
|
||||||
el.rounded_tr(CLIENT_SIDE_DECORATION_ROUNDING)
|
div.rounded_tr(CLIENT_SIDE_DECORATION_ROUNDING)
|
||||||
})
|
})
|
||||||
.when(!(tiling.top || tiling.left), |el| {
|
.when(!(tiling.top || tiling.left), |div| {
|
||||||
el.rounded_tl(CLIENT_SIDE_DECORATION_ROUNDING)
|
div.rounded_tl(CLIENT_SIDE_DECORATION_ROUNDING)
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.bg(color)
|
.bg(color)
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(cx.theme().border)
|
||||||
.content_stretch()
|
.content_stretch()
|
||||||
.child(
|
.child(
|
||||||
div()
|
h_flex()
|
||||||
.id("title-bar")
|
.id("title-bar")
|
||||||
.flex()
|
|
||||||
.flex_row()
|
|
||||||
.items_center()
|
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.w_full()
|
.w_full()
|
||||||
.when(self.platform_kind.is_mac(), |this| {
|
.when(cx.theme().platform.is_mac(), |this| {
|
||||||
this.on_click(|event, window, _| {
|
this.on_click(|event, window, _| {
|
||||||
if event.click_count() == 2 {
|
if event.click_count() == 2 {
|
||||||
window.titlebar_double_click();
|
window.titlebar_double_click();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.when(self.platform_kind.is_linux(), |this| {
|
.when(cx.theme().platform.is_linux(), |this| {
|
||||||
this.on_click(|event, window, _| {
|
this.on_click(|event, window, _| {
|
||||||
if event.click_count() == 2 {
|
if event.click_count() == 2 {
|
||||||
window.zoom_window();
|
window.zoom_window();
|
||||||
@@ -137,45 +129,60 @@ impl Render for TitleBar {
|
|||||||
})
|
})
|
||||||
.children(children),
|
.children(children),
|
||||||
)
|
)
|
||||||
.when(!window.is_fullscreen(), |this| match self.platform_kind {
|
.child(
|
||||||
PlatformKind::Linux => {
|
h_flex()
|
||||||
#[cfg(target_os = "linux")]
|
.absolute()
|
||||||
if matches!(decorations, Decorations::Client { .. }) {
|
.top_0()
|
||||||
this.child(LinuxWindowControls::new(None))
|
.right_0()
|
||||||
.when(supported_controls.window_menu, |this| {
|
.pr_2()
|
||||||
this.on_mouse_down(MouseButton::Right, move |ev, window, _| {
|
.h(height)
|
||||||
window.show_window_menu(ev.position)
|
.child(
|
||||||
})
|
div().when(!window.is_fullscreen(), |this| match cx.theme().platform {
|
||||||
})
|
PlatformKind::Linux => {
|
||||||
.on_mouse_move(cx.listener(move |this, _ev, window, _| {
|
#[cfg(target_os = "linux")]
|
||||||
if this.should_move {
|
if matches!(decorations, Decorations::Client { .. }) {
|
||||||
this.should_move = false;
|
this.child(LinuxWindowControls::new(None))
|
||||||
window.start_window_move();
|
.when(supported_controls.window_menu, |this| {
|
||||||
|
this.on_mouse_down(
|
||||||
|
MouseButton::Right,
|
||||||
|
move |ev, window, _| {
|
||||||
|
window.show_window_menu(ev.position)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.on_mouse_move(cx.listener(move |this, _ev, window, _| {
|
||||||
|
if this.should_move {
|
||||||
|
this.should_move = false;
|
||||||
|
window.start_window_move();
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.on_mouse_down_out(cx.listener(
|
||||||
|
move |this, _ev, _window, _cx| {
|
||||||
|
this.should_move = false;
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.on_mouse_up(
|
||||||
|
MouseButton::Left,
|
||||||
|
cx.listener(move |this, _ev, _window, _cx| {
|
||||||
|
this.should_move = false;
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.on_mouse_down(
|
||||||
|
MouseButton::Left,
|
||||||
|
cx.listener(move |this, _ev, _window, _cx| {
|
||||||
|
this.should_move = true;
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this
|
||||||
}
|
}
|
||||||
}))
|
#[cfg(not(target_os = "linux"))]
|
||||||
.on_mouse_down_out(cx.listener(move |this, _ev, _window, _cx| {
|
this
|
||||||
this.should_move = false;
|
}
|
||||||
}))
|
PlatformKind::Windows => this.child(WindowsWindowControls::new(height)),
|
||||||
.on_mouse_up(
|
PlatformKind::Mac => this,
|
||||||
MouseButton::Left,
|
}),
|
||||||
cx.listener(move |this, _ev, _window, _cx| {
|
),
|
||||||
this.should_move = false;
|
)
|
||||||
}),
|
|
||||||
)
|
|
||||||
.on_mouse_down(
|
|
||||||
MouseButton::Left,
|
|
||||||
cx.listener(move |this, _ev, _window, _cx| {
|
|
||||||
this.should_move = true;
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
|
||||||
this
|
|
||||||
}
|
|
||||||
PlatformKind::Windows => this.child(WindowsWindowControls::new(height)),
|
|
||||||
PlatformKind::Mac => this,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
img, Action, App, InteractiveElement, IntoElement, MouseButton, ParentElement, RenderOnce,
|
svg, Action, App, InteractiveElement, IntoElement, MouseButton, ParentElement, RenderOnce,
|
||||||
StatefulInteractiveElement, Styled, Window,
|
SharedString, StatefulInteractiveElement, Styled, Window,
|
||||||
};
|
};
|
||||||
use linicon::{lookup_icon, IconType};
|
use linicon::{lookup_icon, IconType};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
@@ -26,21 +25,26 @@ impl LinuxWindowControls {
|
|||||||
|
|
||||||
impl RenderOnce for LinuxWindowControls {
|
impl RenderOnce for LinuxWindowControls {
|
||||||
fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||||
|
let supported_controls = window.window_controls();
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id("linux-window-controls")
|
.id("linux-window-controls")
|
||||||
.px_2()
|
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||||
.child(WindowControl::new(
|
.when(supported_controls.minimize, |this| {
|
||||||
LinuxControl::Minimize,
|
this.child(WindowControl::new(
|
||||||
IconName::WindowMinimize,
|
LinuxControl::Minimize,
|
||||||
))
|
IconName::WindowMinimize,
|
||||||
.child({
|
))
|
||||||
if window.is_maximized() {
|
})
|
||||||
WindowControl::new(LinuxControl::Restore, IconName::WindowRestore)
|
.when(supported_controls.maximize, |this| {
|
||||||
} else {
|
this.child({
|
||||||
WindowControl::new(LinuxControl::Maximize, IconName::WindowMaximize)
|
if window.is_maximized() {
|
||||||
}
|
WindowControl::new(LinuxControl::Restore, IconName::WindowRestore)
|
||||||
|
} else {
|
||||||
|
WindowControl::new(LinuxControl::Maximize, IconName::WindowMaximize)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
WindowControl::new(LinuxControl::Close, IconName::WindowClose)
|
WindowControl::new(LinuxControl::Close, IconName::WindowClose)
|
||||||
@@ -87,24 +91,22 @@ impl RenderOnce for WindowControl {
|
|||||||
.justify_center()
|
.justify_center()
|
||||||
.items_center()
|
.items_center()
|
||||||
.rounded_full()
|
.rounded_full()
|
||||||
.map(|this| {
|
.size_6()
|
||||||
if is_gnome {
|
.when(is_gnome, |this| {
|
||||||
this.size_6()
|
this.bg(cx.theme().ghost_element_background_alt)
|
||||||
.bg(cx.theme().tab_inactive_background)
|
.hover(|this| this.bg(cx.theme().ghost_element_hover))
|
||||||
.hover(|this| this.bg(cx.theme().tab_hover_background))
|
.active(|this| this.bg(cx.theme().ghost_element_active))
|
||||||
.active(|this| this.bg(cx.theme().tab_active_background))
|
|
||||||
} else {
|
|
||||||
this.size_5()
|
|
||||||
.bg(cx.theme().ghost_element_background)
|
|
||||||
.hover(|this| this.bg(cx.theme().ghost_element_hover))
|
|
||||||
.active(|this| this.bg(cx.theme().ghost_element_active))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
if let Some(Some(path)) = linux_controls().get(&self.kind).cloned() {
|
if let Some(Some(path)) = linux_controls().get(&self.kind).cloned() {
|
||||||
this.child(img(path).flex_grow().size_4())
|
this.child(
|
||||||
|
svg()
|
||||||
|
.external_path(SharedString::from(path))
|
||||||
|
.size_4()
|
||||||
|
.text_color(cx.theme().text),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
this.child(Icon::new(self.fallback).flex_grow().small())
|
this.child(Icon::new(self.fallback).small().text_color(cx.theme().text))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_mouse_move(|_, _window, cx| cx.stop_propagation())
|
.on_mouse_move(|_, _window, cx| cx.stop_propagation())
|
||||||
@@ -114,20 +116,14 @@ impl RenderOnce for WindowControl {
|
|||||||
LinuxControl::Minimize => window.minimize_window(),
|
LinuxControl::Minimize => window.minimize_window(),
|
||||||
LinuxControl::Restore => window.zoom_window(),
|
LinuxControl::Restore => window.zoom_window(),
|
||||||
LinuxControl::Maximize => window.zoom_window(),
|
LinuxControl::Maximize => window.zoom_window(),
|
||||||
LinuxControl::Close => window.dispatch_action(
|
LinuxControl::Close => cx.quit(),
|
||||||
self.close_action
|
|
||||||
.as_ref()
|
|
||||||
.expect("Use WindowControl::new_close() for close control.")
|
|
||||||
.boxed_clone(),
|
|
||||||
cx,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static DE: OnceLock<DesktopEnvironment> = OnceLock::new();
|
static DE: OnceLock<DesktopEnvironment> = OnceLock::new();
|
||||||
static LINUX_CONTROLS: OnceLock<HashMap<LinuxControl, Option<PathBuf>>> = OnceLock::new();
|
static LINUX_CONTROLS: OnceLock<HashMap<LinuxControl, Option<String>>> = OnceLock::new();
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum DesktopEnvironment {
|
pub enum DesktopEnvironment {
|
||||||
@@ -182,7 +178,7 @@ impl LinuxControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn linux_controls() -> &'static HashMap<LinuxControl, Option<PathBuf>> {
|
fn linux_controls() -> &'static HashMap<LinuxControl, Option<String>> {
|
||||||
LINUX_CONTROLS.get_or_init(|| {
|
LINUX_CONTROLS.get_or_init(|| {
|
||||||
let mut icons = HashMap::new();
|
let mut icons = HashMap::new();
|
||||||
icons.insert(LinuxControl::Close, None);
|
icons.insert(LinuxControl::Close, None);
|
||||||
@@ -219,7 +215,9 @@ fn linux_controls() -> &'static HashMap<LinuxControl, Option<PathBuf>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(Ok(icon)) = control_icon {
|
if let Some(Ok(icon)) = control_icon {
|
||||||
icons.entry(control).and_modify(|v| *v = Some(icon.path));
|
icons
|
||||||
|
.entry(control)
|
||||||
|
.and_modify(|v| *v = Some(icon.path.to_string_lossy().to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.91"
|
channel = "1.92"
|
||||||
profile = "minimal"
|
profile = "minimal"
|
||||||
components = ["rustfmt", "clippy"]
|
components = ["rustfmt", "clippy"]
|
||||||
targets = [
|
targets = [
|
||||||
|
|||||||
Reference in New Issue
Block a user