Files
signed/crates/workspace/src/views/repo/init_dialog.rs
T
reya d68c098818
Rust / build (macos-latest, stable) (push) Waiting to run
Rust / build (ubuntu-latest, stable) (push) Waiting to run
Rust / build (windows-latest, stable) (push) Waiting to run
restructure
2026-09-12 18:26:06 +07:00

208 lines
7.9 KiB
Rust

use std::path::PathBuf;
use assets::CustomIconName;
use gpui::prelude::*;
use gpui::{App, Entity, WeakEntity, Window, px};
use gpui_base::h_flex;
use gpui_base::input::TextareaState;
use gpui_component::button::{Button, ButtonVariants};
use gpui_component::dialog::{DialogDescription, DialogFooter, DialogHeader, DialogTitle};
use gpui_component::form::{field, v_form};
use gpui_component::input::{Input, InputState, Textarea};
use gpui_component::{ActiveTheme, Disableable, WindowExt};
use settings::SettingsStore;
use signed_state::Backend;
use super::RepoDetailView;
use crate::views::dialog_state::{DialogProgress, error_row};
use crate::views::sidebar::grasp_servers::{
GraspServersState, grasp_servers_field, load_user_grasp_servers,
};
/// Shared state for the Init dialog, so async results can be rendered.
pub type InitRepoState = DialogProgress;
/// Open the Init dialog for the local repository at `local_path`.
pub fn open(
local_path: PathBuf,
view: WeakEntity<RepoDetailView>,
window: &mut Window,
cx: &mut App,
) {
let default_name = local_path
.file_name()
.map(|name| name.to_string_lossy().into_owned())
.unwrap_or_default();
let grasp_settings = SettingsStore::global(cx)
.read(cx)
.settings()
.grasp_servers
.clone();
let state = cx.new(|_| InitRepoState::default());
let grasp_state = cx.new(|_| GraspServersState::new_default(&grasp_settings));
let relay_input = cx.new(|cx| InputState::new(window, cx).placeholder("relay.example.com"));
let name_input = cx.new(|cx| InputState::new(window, cx).default_value(default_name));
let desc_input = cx.new(|cx| {
TextareaState::new(window, cx)
.auto_grow(3, 5)
.placeholder("Short description")
});
// Load the user's grasp servers.
load_user_grasp_servers(grasp_state.clone(), window, cx);
window.open_dialog(cx, move |dialog, _window, _cx| {
const DESC: &str = "Publish this local repository to Nostr.";
let name_input = name_input.clone();
let desc_input = desc_input.clone();
let relay_input = relay_input.clone();
let state = state.clone();
let grasp_state = grasp_state.clone();
let local_path = local_path.clone();
let view = view.clone();
dialog
.width(px(520.))
.margin_top(px(50.))
.content(move |content, _window, cx| {
let busy = state.read(cx).busy;
let error = state.read(cx).error.clone();
content
.child(
DialogHeader::new()
.child(DialogTitle::new().child("Initialize repository"))
.child(DialogDescription::new().child(DESC)),
)
.child(
v_form()
.child(
field()
.label("Repository name")
.description("Max 100 characters")
.required(true)
.child(Input::new(&name_input).readonly(true)),
)
.child(
field()
.label("Description")
.child(Textarea::new(&desc_input)),
)
.child(
field()
.label("Folder")
.description("The local repository being published")
.child(
h_flex()
.h_8()
.w_full()
.px_2()
.bg(cx.theme().muted)
.text_sm()
.text_color(cx.theme().muted_foreground)
.rounded(cx.theme().radius)
.child(local_path.display().to_string()),
),
)
.child(grasp_servers_field(&grasp_state, &relay_input, cx)),
)
.children(error_row(&error, cx))
.child(
DialogFooter::new().justify_end().child(
Button::new("init")
.primary()
.label("Initialize")
.icon(CustomIconName::Init)
.tooltip("Publish to Nostr")
.loading(busy)
.disabled(busy)
.on_click({
let name_input = name_input.clone();
let desc_input = desc_input.clone();
let state = state.clone();
let grasp_state = grasp_state.clone();
let local_path = local_path.clone();
let view = view.clone();
move |_ev, window, cx| {
init_repository(
local_path.clone(),
(name_input.clone(), desc_input.clone()),
state.clone(),
grasp_state.clone(),
view.clone(),
window,
cx,
);
}
}),
),
)
})
});
}
/// Run the init flow.
///
/// Closes the dialog and switches the repository into NIP-34 mode on success.
fn init_repository(
local_path: PathBuf,
inputs: (Entity<InputState>, Entity<TextareaState>),
state: Entity<InitRepoState>,
grasp_state: Entity<GraspServersState>,
view: WeakEntity<RepoDetailView>,
window: &mut Window,
cx: &mut App,
) {
let (name_input, desc_input) = inputs;
let name = name_input.read(cx).value().trim().to_owned();
let description = desc_input.read(cx).value().trim().to_owned();
let servers = grasp_state.read(cx).grasp_servers.clone();
if name.is_empty() {
state.update(cx, |state, _| state.fail("Repository name is required"));
return;
}
if servers.is_empty() {
state.update(cx, |state, _| state.fail("Add at least one grasp server"));
return;
}
state.update(cx, |state, _| state.begin());
let backend = Backend::global(cx);
let task = backend.update(cx, |backend, cx| {
backend.publish_local_repo(local_path.clone(), &name, &description, servers, cx)
});
let handle = window.window_handle();
let state = state.clone();
let view = view.clone();
cx.spawn(async move |cx| match task.await {
Ok(announcement) => {
cx.update_window(handle, |_, window, cx| {
window.close_dialog(cx);
if let Some(view) = view.upgrade() {
view.update(cx, |this, cx| {
this.apply_announcement(announcement, cx);
});
}
})
.ok();
}
Err(e) => {
cx.update_window(handle, |_, _window, cx| {
state.update(cx, |state, _| state.fail(e.to_string()));
})
.ok();
}
})
.detach();
}