use crate::encoding::parse_address;
use crate::app::dom;
pub(super) async fn run_set_public_face(choice: &str) {
let msg = "publish-app-msg";
let set_err = |m: &str| {
dom::swap_inner(msg, &dom::msg_span(dom::Msg::Error, m));
};
let Some(name) = crate::app::tenant::current_name() else {
set_err("only on a subdomain");
return;
};
let verified_eoa = crate::app::APP.with(|cell| {
use crate::app::VerifyState;
match &cell.borrow().verify_state {
VerifyState::Verified { address } => Some(address.clone()),
_ => None,
}
});
let id = match crate::app::registry::id_of_name(&name).await {
Ok(id) if id != 0 => id,
_ => {
set_err("name isn't registered on-chain");
return;
}
};
let registry_addr = match parse_address(crate::app::registry::REGISTRY_ADDRESS) {
Ok(a) => a,
Err(e) => {
set_err(&e);
return;
}
};
let mk = |input: Vec<u8>| crate::tempo_tx::TempoCall {
to: registry_addr,
value_wei: 0,
input,
};
let (calls, gas): (Vec<crate::tempo_tx::TempoCall>, u128) = match choice {
"directory" => (
vec![mk(crate::app::registry::encode_set_public_face(id, "directory"))],
500_000,
),
"app" => {
let fs = crate::app::shared_opfs();
let src = match fs.read("app.rl").await {
Ok(b) if !b.is_empty() => String::from_utf8_lossy(&b).into_owned(),
_ => {
set_err("no app.rl on this device — build one first (run_cartridge)");
return;
}
};
let wasm = match crate::rustlite::compile(&src) {
Ok(w) => w,
Err(e) => {
let loc = e
.location(&src)
.map(|l| format!(" ({l})"))
.unwrap_or_default();
set_err(&format!("compile: {e}{loc}"));
return;
}
};
if wasm.len() > 16_384 {
set_err("app wasm too large to publish (max 16 KB)");
return;
}
(
vec![
mk(crate::app::registry::encode_set_app_wasm(id, &wasm)),
mk(crate::app::registry::encode_set_public_face(id, "app")),
],
crate::app::gas::set_metadata_gas(wasm.len()),
)
}
"html" => {
let fs = crate::app::shared_opfs();
let html = match fs.read("index.html").await {
Ok(b) if !b.is_empty() => b,
_ => {
set_err("no index.html on this device — create one first");
return;
}
};
if html.len() > 24_576 {
set_err("index.html too large to publish (max 24 KB)");
return;
}
(
vec![
mk(crate::app::registry::encode_set_public_html(id, &html)),
mk(crate::app::registry::encode_set_public_face(id, "html")),
],
crate::app::gas::set_metadata_gas(html.len()),
)
}
_ => {
set_err("unknown public face");
return;
}
};
dom::swap_inner(msg, "<span style=\"color:var(--muted)\">saving…</span>");
let on_chain_owner = match crate::app::registry::owner_of_name(&name).await {
Ok(Some(o)) => o,
_ => {
set_err("name isn't registered on-chain");
return;
}
};
let local = crate::app::chat::credit_signer().await;
let is_signer = match &local {
Some((_, addr)) => {
let addr_hex = crate::encoding::bytes_to_hex_str(addr);
crate::app::registry::is_authorized_signer(&on_chain_owner, &addr_hex)
.await
.unwrap_or(false)
}
None => false,
};
let result = if is_signer {
let (signer, _) = local.unwrap();
let fee_payer = match crate::app::sponsor::signer() {
Ok(s) => s,
Err(e) => {
set_err(&e);
return;
}
};
let token_id = match crate::app::registry::tba_token_id_of(&on_chain_owner).await {
Ok(t) => t,
Err(e) => {
set_err(&e);
return;
}
};
let targets: Vec<([u8; 20], Vec<u8>)> =
calls.iter().map(|c| (registry_addr, c.input.clone())).collect();
crate::app::registry::tba_execute_batch_sponsored(
&signer,
&fee_payer,
token_id,
&on_chain_owner,
&targets,
crate::app::registry::ALPHA_USD_ADDRESS,
gas + 800_000,
)
.await
} else if let Some(owner_hex) =
verified_eoa.filter(|a| a.eq_ignore_ascii_case(&on_chain_owner))
{
super::run_sponsored_tempo_call(&owner_hex, calls, gas, "public face").await
} else {
set_err("verify as owner first");
return;
};
match result {
Ok(_tx) => {
if choice == "directory" {
dom::swap_inner(
msg,
&maud::html! {
span style="color:var(--fg)" {
"public face → directory ✓ "
a href=(format!("https://{name}.localharness.xyz/"))
target="_blank" rel="noopener" style="color:var(--accent)" {
"open →"
}
}
}
.into_string(),
);
} else {
dom::swap_inner(
msg,
&crate::app::templates::publish_share_fragment(&name).into_string(),
);
}
super::admin::refresh_public_face_status().await;
}
Err(e) => set_err(&format!("failed: {e}")),
}
}
pub(super) async fn run_copy_to_clipboard(text: &str, flip_id: &str) {
let Some(win) = web_sys::window() else { return };
let promise = win.navigator().clipboard().write_text(text);
if wasm_bindgen_futures::JsFuture::from(promise).await.is_ok() {
dom::swap_inner(flip_id, "copied ✓");
}
}