feat(web): global privacy control, 4 privacies
This commit is contained in:
parent
f57f304419
commit
870c781cf7
6 changed files with 214 additions and 115 deletions
|
@ -317,6 +317,16 @@
|
||||||
color: var(--background);
|
color: var(--background);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
input[type="range"] {
|
||||||
|
accent-color: var(--accent);
|
||||||
|
}
|
||||||
|
input[type="range"]:hover {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
input[type="range"]:focus {
|
||||||
|
outline: none;
|
||||||
|
accent-color: var(--accent-dim);
|
||||||
|
}
|
||||||
.ml-1-r {
|
.ml-1-r {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
|
@ -377,6 +387,9 @@
|
||||||
color: unset;
|
color: unset;
|
||||||
text-shadow: unset;
|
text-shadow: unset;
|
||||||
}
|
}
|
||||||
|
span.big-emoji {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
div.context {
|
div.context {
|
||||||
border-left: 1px solid var(--background-dim);
|
border-left: 1px solid var(--background-dim);
|
||||||
padding-left: 1px;
|
padding-left: 1px;
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use apb::{target::Addressed, Activity, ActivityMut, Base, Object};
|
use apb::{Activity, ActivityMut, Base, Object};
|
||||||
|
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ActivityLine(activity: crate::Object, children: Children) -> impl IntoView {
|
pub fn ActivityLine(activity: crate::Object, children: Children) -> impl IntoView {
|
||||||
let object_id = activity.object().id().unwrap_or_default();
|
let object_id = activity.object().id().unwrap_or_default();
|
||||||
|
let to = activity.to().all_ids();
|
||||||
|
let cc = activity.cc().all_ids();
|
||||||
|
let privacy = Privacy::from_addressed(&to, &cc);
|
||||||
let activity_url = activity.id().map(|x| view! {
|
let activity_url = activity.id().map(|x| view! {
|
||||||
<sup><small><a class="clean ml-s" href={x.to_string()} target="_blank">"↗"</a></small></sup>
|
<sup><small><a class="clean ml-s" href={x.to_string()} target="_blank">"↗"</a></small></sup>
|
||||||
});
|
});
|
||||||
|
@ -31,7 +34,7 @@ pub fn ActivityLine(activity: crate::Object, children: Children) -> impl IntoVie
|
||||||
{kind.as_ref().to_string()}
|
{kind.as_ref().to_string()}
|
||||||
</a>
|
</a>
|
||||||
{activity_url}
|
{activity_url}
|
||||||
<PrivacyMarker addressed=activity.addressed() />
|
<PrivacyMarker privacy=privacy to=&to cc=&cc />
|
||||||
</code>
|
</code>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -74,6 +74,8 @@ pub fn App() -> impl IntoView {
|
||||||
);
|
);
|
||||||
let (config, set_config, _) = use_local_storage::<crate::Config, JsonSerdeCodec>("config");
|
let (config, set_config, _) = use_local_storage::<crate::Config, JsonSerdeCodec>("config");
|
||||||
|
|
||||||
|
let (privacy, set_privacy) = create_signal(Privacy::Private);
|
||||||
|
|
||||||
let auth = Auth { token, userid };
|
let auth = Auth { token, userid };
|
||||||
|
|
||||||
let username = auth.userid.get_untracked()
|
let username = auth.userid.get_untracked()
|
||||||
|
@ -85,6 +87,7 @@ pub fn App() -> impl IntoView {
|
||||||
provide_context(auth);
|
provide_context(auth);
|
||||||
provide_context(config);
|
provide_context(config);
|
||||||
provide_context(feeds);
|
provide_context(feeds);
|
||||||
|
provide_context(privacy);
|
||||||
|
|
||||||
let reply_controls = ReplyControls::default();
|
let reply_controls = ReplyControls::default();
|
||||||
provide_context(reply_controls);
|
provide_context(reply_controls);
|
||||||
|
@ -136,6 +139,8 @@ pub fn App() -> impl IntoView {
|
||||||
/>
|
/>
|
||||||
<hr class="mt-1 mb-1" />
|
<hr class="mt-1 mb-1" />
|
||||||
<div class:hidden=move || !auth.present() >
|
<div class:hidden=move || !auth.present() >
|
||||||
|
<PrivacySelector setter=set_privacy />
|
||||||
|
<hr class="mt-1 mb-1" />
|
||||||
{move || if advanced.get() { view! {
|
{move || if advanced.get() { view! {
|
||||||
<AdvancedPostBox advanced=set_advanced/>
|
<AdvancedPostBox advanced=set_advanced/>
|
||||||
}} else { view! {
|
}} else { view! {
|
||||||
|
|
|
@ -37,21 +37,17 @@ pub fn DateTime(t: Option<chrono::DateTime<chrono::Utc>>) -> impl IntoView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const PRIVACY_PUBLIC : &str = "🪩";
|
|
||||||
pub const PRIVACY_FOLLOWERS : &str = "🔒";
|
|
||||||
pub const PRIVACY_PRIVATE : &str = "📨";
|
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn PrivacyMarker(addressed: Vec<String>) -> impl IntoView {
|
pub fn PrivacyMarker<'a>(
|
||||||
let privacy = if addressed.iter().any(|x| x == apb::target::PUBLIC) {
|
privacy: Privacy,
|
||||||
PRIVACY_PUBLIC
|
#[prop(optional)] to: &'a [String],
|
||||||
} else if addressed.iter().any(|x| x.ends_with("/followers")) {
|
#[prop(optional)] cc: &'a [String],
|
||||||
PRIVACY_FOLLOWERS
|
#[prop(optional)] big: bool,
|
||||||
} else {
|
) -> impl IntoView {
|
||||||
PRIVACY_PRIVATE
|
let to_txt = if to.is_empty() { String::new() } else { format!("to: {}", to.join(", ")) };
|
||||||
};
|
let cc_txt = if cc.is_empty() { String::new() } else { format!("cc: {}", cc.join(", ")) };
|
||||||
let audience = format!("[ {} ]", addressed.join(", "));
|
let audience = format!("{to_txt}\n{cc_txt}");
|
||||||
view! {
|
view! {
|
||||||
<span class="emoji ml-1 mr-s moreinfo" title={audience} >{privacy}</span>
|
<span class:big-emoji=big class="emoji ml-1 mr-s moreinfo" title={audience} >{privacy.icon()}</span>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,17 +35,122 @@ struct MentionMatch {
|
||||||
domain: String,
|
domain: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type PrivacyControl = ReadSignal<Privacy>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Privacy {
|
||||||
|
Broadcast = 4,
|
||||||
|
Public = 3,
|
||||||
|
Private = 2,
|
||||||
|
Direct = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Privacy {
|
||||||
|
pub fn is_public(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Broadcast | Self::Public => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_value(v: &str) -> Self {
|
||||||
|
match v {
|
||||||
|
"1" => Self::Direct,
|
||||||
|
"2" => Self::Private,
|
||||||
|
"3" => Self::Public,
|
||||||
|
"4" => Self::Broadcast,
|
||||||
|
_ => panic!("invalid value for privacy"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_addressed(to: &[String], cc: &[String]) -> Self {
|
||||||
|
if to.iter().any(|x| x == apb::target::PUBLIC) {
|
||||||
|
return Self::Broadcast;
|
||||||
|
}
|
||||||
|
if cc.iter().any(|x| x == apb::target::PUBLIC) {
|
||||||
|
return Self::Public;
|
||||||
|
}
|
||||||
|
if to.iter().any(|x| x.ends_with("/followers"))
|
||||||
|
|| cc.iter().any(|x| x.ends_with("/followers")) {
|
||||||
|
return Self::Private;
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::Direct
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn icon(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Broadcast => "📢",
|
||||||
|
Self::Public => "🪩",
|
||||||
|
Self::Private => "🔒",
|
||||||
|
Self::Direct => "📨",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn address(&self, user: &str) -> (Vec<String>, Vec<String>) {
|
||||||
|
match self {
|
||||||
|
Self::Broadcast => (
|
||||||
|
vec![apb::target::PUBLIC.to_string()],
|
||||||
|
vec![format!("{URL_BASE}/actors/{user}/followers")],
|
||||||
|
),
|
||||||
|
Self::Public => (
|
||||||
|
vec![],
|
||||||
|
vec![apb::target::PUBLIC.to_string(), format!("{URL_BASE}/actors/{user}/followers")],
|
||||||
|
),
|
||||||
|
Self::Private => (
|
||||||
|
vec![],
|
||||||
|
vec![format!("{URL_BASE}/actors/{user}/followers")],
|
||||||
|
),
|
||||||
|
Self::Direct => (
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn PrivacySelector(setter: WriteSignal<Privacy>) -> impl IntoView {
|
||||||
|
let privacy = use_context::<PrivacyControl>().expect("missing privacy context");
|
||||||
|
let auth = use_context::<Auth>().expect("missing auth context");
|
||||||
|
view! {
|
||||||
|
<table class="align w-100">
|
||||||
|
<tr>
|
||||||
|
<td class="w-100">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="1"
|
||||||
|
max="4"
|
||||||
|
class="w-100"
|
||||||
|
prop:value=move || privacy.get() as u8
|
||||||
|
on:input=move |ev| {
|
||||||
|
ev.prevent_default();
|
||||||
|
setter.set(Privacy::from_value(&event_target_value(&ev)));
|
||||||
|
} />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{move || {
|
||||||
|
let p = privacy.get();
|
||||||
|
let (to, cc) = p.address(&auth.username());
|
||||||
|
view! {
|
||||||
|
<PrivacyMarker privacy=p to=&to cc=&cc big=true />
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn PostBox(advanced: WriteSignal<bool>) -> impl IntoView {
|
pub fn PostBox(advanced: WriteSignal<bool>) -> impl IntoView {
|
||||||
let auth = use_context::<Auth>().expect("missing auth context");
|
let auth = use_context::<Auth>().expect("missing auth context");
|
||||||
|
let privacy = use_context::<PrivacyControl>().expect("missing privacy context");
|
||||||
let reply = use_context::<ReplyControls>().expect("missing reply controls");
|
let reply = use_context::<ReplyControls>().expect("missing reply controls");
|
||||||
let (posting, set_posting) = create_signal(false);
|
let (posting, set_posting) = create_signal(false);
|
||||||
let (error, set_error) = create_signal(None);
|
let (error, set_error) = create_signal(None);
|
||||||
let (content, set_content) = create_signal("".to_string());
|
let (content, set_content) = create_signal("".to_string());
|
||||||
let summary_ref: NodeRef<html::Input> = create_node_ref();
|
let summary_ref: NodeRef<html::Input> = create_node_ref();
|
||||||
let public_ref: NodeRef<html::Input> = create_node_ref();
|
|
||||||
let followers_ref: NodeRef<html::Input> = create_node_ref();
|
|
||||||
let private_ref: NodeRef<html::Input> = create_node_ref();
|
|
||||||
|
|
||||||
// TODO is this too abusive with resources? im even checking if TLD exists...
|
// TODO is this too abusive with resources? im even checking if TLD exists...
|
||||||
let mentions = create_local_resource(
|
let mentions = create_local_resource(
|
||||||
|
@ -109,27 +214,12 @@ pub fn PostBox(advanced: WriteSignal<bool>) -> impl IntoView {
|
||||||
on:input=move |ev| set_content.set(event_target_value(&ev))
|
on:input=move |ev| set_content.set(event_target_value(&ev))
|
||||||
></textarea>
|
></textarea>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<table class="align rev w-100">
|
|
||||||
<tr>
|
|
||||||
<td><input id="priv-public" type="radio" name="privacy" value="public" title="public" node_ref=public_ref /></td>
|
|
||||||
<td><span class="emoji" title="public" >{PRIVACY_PUBLIC}</span></td>
|
|
||||||
<td class="w-100" rowspan="3">
|
|
||||||
<button class="w-100" prop:disabled=posting type="button" style="height: 3em" on:click=move |_| {
|
<button class="w-100" prop:disabled=posting type="button" style="height: 3em" on:click=move |_| {
|
||||||
set_posting.set(true);
|
set_posting.set(true);
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
let summary = get_if_some(summary_ref);
|
let summary = get_if_some(summary_ref);
|
||||||
let content = content.get();
|
let content = content.get();
|
||||||
let mut cc_vec = Vec::new();
|
let (mut to_vec, cc_vec) = privacy.get().address(&auth.username());
|
||||||
let mut to_vec = Vec::new();
|
|
||||||
if get_checked(followers_ref) {
|
|
||||||
cc_vec.push(format!("{URL_BASE}/actors/{}/followers", auth.username()));
|
|
||||||
}
|
|
||||||
if get_checked(public_ref) {
|
|
||||||
cc_vec.push(apb::target::PUBLIC.to_string());
|
|
||||||
cc_vec.push(format!("{URL_BASE}/actors/{}/followers", auth.username()));
|
|
||||||
}
|
|
||||||
let mut mention_tags : Vec<serde_json::Value> = mentions.get()
|
let mut mention_tags : Vec<serde_json::Value> = mentions.get()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -180,17 +270,7 @@ pub fn PostBox(advanced: WriteSignal<bool>) -> impl IntoView {
|
||||||
set_posting.set(false);
|
set_posting.set(false);
|
||||||
})
|
})
|
||||||
} >post</button>
|
} >post</button>
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><input id="priv-followers" type="radio" name="privacy" value="followers" title="followers" node_ref=followers_ref checked /></td>
|
|
||||||
<td><span class="emoji" title="followers" >{PRIVACY_FOLLOWERS}</span></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><input id="priv-private" type="radio" name="privacy" value="private" title="private" node_ref=private_ref /></td>
|
|
||||||
<td><span class="emoji" title="private" >{PRIVACY_PRIVATE}</span></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
{move|| error.get().map(|x| view! { <blockquote class="mt-s">{x}</blockquote> })}
|
{move|| error.get().map(|x| view! { <blockquote class="mt-s">{x}</blockquote> })}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,9 @@ pub fn Object(object: crate::Object) -> impl IntoView {
|
||||||
let author_id = object.attributed_to().id().ok().unwrap_or_default();
|
let author_id = object.attributed_to().id().ok().unwrap_or_default();
|
||||||
let author = cache::OBJECTS.get_or(&author_id, serde_json::Value::String(author_id.clone()).into());
|
let author = cache::OBJECTS.get_or(&author_id, serde_json::Value::String(author_id.clone()).into());
|
||||||
let sensitive = object.sensitive().unwrap_or_default();
|
let sensitive = object.sensitive().unwrap_or_default();
|
||||||
let addressed = object.addressed();
|
let to = object.to().all_ids();
|
||||||
let public = addressed.iter().any(|x| x.as_str() == apb::target::PUBLIC);
|
let cc = object.cc().all_ids();
|
||||||
|
let privacy = Privacy::from_addressed(&to, &cc);
|
||||||
let external_url = object.url().id().ok().unwrap_or_else(|| oid.clone());
|
let external_url = object.url().id().ok().unwrap_or_else(|| oid.clone());
|
||||||
let attachments = object.attachment()
|
let attachments = object.attachment()
|
||||||
.flat()
|
.flat()
|
||||||
|
@ -180,7 +181,7 @@ pub fn Object(object: crate::Object) -> impl IntoView {
|
||||||
{object.in_reply_to().id().ok().map(|reply| view! {
|
{object.in_reply_to().id().ok().map(|reply| view! {
|
||||||
<small><i><a class="clean" href={Uri::web(U::Object, &reply)} title={reply}>reply</a></i></small>
|
<small><i><a class="clean" href={Uri::web(U::Object, &reply)} title={reply}>reply</a></i></small>
|
||||||
})}
|
})}
|
||||||
<PrivacyMarker addressed=addressed />
|
<PrivacyMarker privacy=privacy to=&to cc=&cc />
|
||||||
<a class="clean hover ml-s" href={Uri::web(U::Object, &object.id().unwrap_or_default())}>
|
<a class="clean hover ml-s" href={Uri::web(U::Object, &object.id().unwrap_or_default())}>
|
||||||
<DateTime t=object.published().ok() />
|
<DateTime t=object.published().ok() />
|
||||||
</a>
|
</a>
|
||||||
|
@ -195,8 +196,8 @@ pub fn Object(object: crate::Object) -> impl IntoView {
|
||||||
{audience_badge}
|
{audience_badge}
|
||||||
<span style="white-space:nowrap">
|
<span style="white-space:nowrap">
|
||||||
<ReplyButton n=comments target=oid.clone() />
|
<ReplyButton n=comments target=oid.clone() />
|
||||||
<LikeButton n=likes liked=already_liked target=oid.clone() author=author_id private=!public />
|
<LikeButton n=likes liked=already_liked target=oid.clone() author=author_id.clone() private=!privacy.is_public() />
|
||||||
<RepostButton n=shares target=oid />
|
{if privacy.is_public() { Some(view! { <RepostButton n=shares target=oid author=author_id /> }) } else { None }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -230,6 +231,7 @@ pub fn LikeButton(
|
||||||
let (count, set_count) = create_signal(n);
|
let (count, set_count) = create_signal(n);
|
||||||
let (clicked, set_clicked) = create_signal(!liked);
|
let (clicked, set_clicked) = create_signal(!liked);
|
||||||
let auth = use_context::<Auth>().expect("missing auth context");
|
let auth = use_context::<Auth>().expect("missing auth context");
|
||||||
|
let privacy = use_context::<PrivacyControl>().expect("missing privacy context");
|
||||||
view! {
|
view! {
|
||||||
<span
|
<span
|
||||||
class:emoji=clicked
|
class:emoji=clicked
|
||||||
|
@ -239,18 +241,17 @@ pub fn LikeButton(
|
||||||
on:click=move |_ev| {
|
on:click=move |_ev| {
|
||||||
if !auth.present() { return; }
|
if !auth.present() { return; }
|
||||||
if !clicked.get() { return; }
|
if !clicked.get() { return; }
|
||||||
let to = apb::Node::links(vec![author.to_string()]);
|
let (mut to, cc) = if private {
|
||||||
let cc = if private { apb::Node::Empty } else {
|
(vec![], vec![])
|
||||||
apb::Node::links(vec![
|
} else {
|
||||||
apb::target::PUBLIC.to_string(),
|
privacy.get().address(&auth.username())
|
||||||
format!("{URL_BASE}/actors/{}/followers", auth.username())
|
|
||||||
])
|
|
||||||
};
|
};
|
||||||
|
to.push(author.clone());
|
||||||
let payload = serde_json::Value::Object(serde_json::Map::default())
|
let payload = serde_json::Value::Object(serde_json::Map::default())
|
||||||
.set_activity_type(Some(apb::ActivityType::Like))
|
.set_activity_type(Some(apb::ActivityType::Like))
|
||||||
.set_object(apb::Node::link(target.clone()))
|
.set_object(apb::Node::link(target.clone()))
|
||||||
.set_to(to)
|
.set_to(apb::Node::links(to))
|
||||||
.set_cc(cc);
|
.set_cc(apb::Node::links(cc));
|
||||||
let target = target.clone();
|
let target = target.clone();
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
match Http::post(&auth.outbox(), &payload, auth).await {
|
match Http::post(&auth.outbox(), &payload, auth).await {
|
||||||
|
@ -304,10 +305,11 @@ pub fn ReplyButton(n: i32, target: String) -> impl IntoView {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn RepostButton(n: i32, target: String) -> impl IntoView {
|
pub fn RepostButton(n: i32, target: String, author: String) -> impl IntoView {
|
||||||
let (count, set_count) = create_signal(n);
|
let (count, set_count) = create_signal(n);
|
||||||
let (clicked, set_clicked) = create_signal(true);
|
let (clicked, set_clicked) = create_signal(true);
|
||||||
let auth = use_context::<Auth>().expect("missing auth context");
|
let auth = use_context::<Auth>().expect("missing auth context");
|
||||||
|
let privacy = use_context::<PrivacyControl>().expect("missing privacy context");
|
||||||
view! {
|
view! {
|
||||||
<span
|
<span
|
||||||
class:emoji=clicked
|
class:emoji=clicked
|
||||||
|
@ -318,13 +320,13 @@ pub fn RepostButton(n: i32, target: String) -> impl IntoView {
|
||||||
if !auth.present() { return; }
|
if !auth.present() { return; }
|
||||||
if !clicked.get() { return; }
|
if !clicked.get() { return; }
|
||||||
set_clicked.set(false);
|
set_clicked.set(false);
|
||||||
let to = apb::Node::links(vec![apb::target::PUBLIC.to_string()]);
|
let (mut to, cc) = privacy.get().address(&auth.username());
|
||||||
let cc = apb::Node::links(vec![format!("{URL_BASE}/actors/{}/followers", auth.username())]);
|
to.push(author.clone());
|
||||||
let payload = serde_json::Value::Object(serde_json::Map::default())
|
let payload = serde_json::Value::Object(serde_json::Map::default())
|
||||||
.set_activity_type(Some(apb::ActivityType::Announce))
|
.set_activity_type(Some(apb::ActivityType::Announce))
|
||||||
.set_object(apb::Node::link(target.clone()))
|
.set_object(apb::Node::link(target.clone()))
|
||||||
.set_to(to)
|
.set_to(apb::Node::links(to))
|
||||||
.set_cc(cc);
|
.set_cc(apb::Node::links(cc));
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
match Http::post(&auth.outbox(), &payload, auth).await {
|
match Http::post(&auth.outbox(), &payload, auth).await {
|
||||||
Ok(()) => set_count.set(count.get() + 1),
|
Ok(()) => set_count.set(count.get() + 1),
|
||||||
|
|
Loading…
Reference in a new issue