mirror of
https://github.com/mikedilger/gossip.git
synced 2024-09-29 00:11:01 +00:00
Mute List made to operate like Contact List does
This commit is contained in:
parent
27cd06f6a7
commit
f225fe8bf4
@ -11,6 +11,7 @@ pub enum ToOverlordMessage {
|
||||
AdvertiseRelayList,
|
||||
ChangePassphrase(String, String),
|
||||
ClearFollowing,
|
||||
ClearMuteList,
|
||||
DelegationReset,
|
||||
DeletePost(Id),
|
||||
DeletePriv,
|
||||
@ -35,6 +36,7 @@ pub enum ToOverlordMessage {
|
||||
PruneDatabase,
|
||||
PushFollow,
|
||||
PushMetadata(Metadata),
|
||||
PushMuteList,
|
||||
ReengageMinion(RelayUrl, Vec<RelayJob>),
|
||||
RefreshFollowedMetadata,
|
||||
ClearAllUsageOnRelay(RelayUrl),
|
||||
@ -47,6 +49,7 @@ pub enum ToOverlordMessage {
|
||||
Shutdown,
|
||||
UnlockKey(String),
|
||||
UpdateFollowing(bool),
|
||||
UpdateMuteList(bool),
|
||||
UpdateMetadata(PublicKey),
|
||||
UpdateMetadataInBulk(Vec<PublicKey>),
|
||||
VisibleNotesChanged(Vec<Id>),
|
||||
@ -105,6 +108,7 @@ pub enum RelayConnectionReason {
|
||||
PostContacts,
|
||||
PostLike,
|
||||
PostMetadata,
|
||||
PostMuteList,
|
||||
ReadThread,
|
||||
}
|
||||
|
||||
@ -131,6 +135,7 @@ impl RelayConnectionReason {
|
||||
PostLike => "Posting a reaction to an event",
|
||||
FetchContacts => "Fetching our contact list",
|
||||
PostContacts => "Posting our contact list",
|
||||
PostMuteList => "Posting our mute list",
|
||||
PostMetadata => "Posting our metadata",
|
||||
ReadThread => "Reading ancestors to build a thread",
|
||||
}
|
||||
@ -151,6 +156,7 @@ impl RelayConnectionReason {
|
||||
PostLike => false,
|
||||
FetchContacts => false,
|
||||
PostContacts => false,
|
||||
PostMuteList => false,
|
||||
PostMetadata => false,
|
||||
ReadThread => true,
|
||||
}
|
||||
|
@ -624,6 +624,7 @@ impl Minion {
|
||||
EventKind::Metadata,
|
||||
//EventKind::RecommendRelay,
|
||||
EventKind::ContactList,
|
||||
EventKind::MuteList,
|
||||
EventKind::RelayList,
|
||||
],
|
||||
// these are all replaceable, no since required
|
||||
|
@ -532,6 +532,9 @@ impl Overlord {
|
||||
ToOverlordMessage::ClearFollowing => {
|
||||
self.clear_following().await?;
|
||||
}
|
||||
ToOverlordMessage::ClearMuteList => {
|
||||
self.clear_mute_list().await?;
|
||||
}
|
||||
ToOverlordMessage::DelegationReset => {
|
||||
Self::delegation_reset().await?;
|
||||
}
|
||||
@ -754,6 +757,9 @@ impl Overlord {
|
||||
ToOverlordMessage::PushMetadata(metadata) => {
|
||||
self.push_metadata(metadata).await?;
|
||||
}
|
||||
ToOverlordMessage::PushMuteList => {
|
||||
self.push_mute_list().await?;
|
||||
}
|
||||
ToOverlordMessage::RankRelay(relay_url, rank) => {
|
||||
if let Some(mut relay) = GLOBALS.storage.read_relay(&relay_url)? {
|
||||
relay.rank = rank as u64;
|
||||
@ -856,6 +862,9 @@ impl Overlord {
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
ToOverlordMessage::UpdateMuteList(merge) => {
|
||||
self.update_mute_list(merge).await?;
|
||||
}
|
||||
ToOverlordMessage::VisibleNotesChanged(visible) => {
|
||||
let visible: Vec<IdHex> = visible.iter().map(|i| (*i).into()).collect();
|
||||
|
||||
@ -1396,6 +1405,42 @@ impl Overlord {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn push_mute_list(&mut self) -> Result<(), Error> {
|
||||
let event = GLOBALS.people.generate_mute_list_event().await?;
|
||||
|
||||
// process event locally
|
||||
crate::process::process_new_event(&event, None, None, false, false).await?;
|
||||
|
||||
// Push to all of the relays we post to
|
||||
let relays: Vec<Relay> = GLOBALS
|
||||
.storage
|
||||
.filter_relays(|r| r.has_usage_bits(Relay::WRITE) && r.rank != 0)?;
|
||||
|
||||
for relay in relays {
|
||||
// Send it the event to pull our followers
|
||||
tracing::debug!("Pushing MuteList to {}", &relay.url);
|
||||
|
||||
self.engage_minion(
|
||||
relay.url.clone(),
|
||||
vec![RelayJob {
|
||||
reason: RelayConnectionReason::PostMuteList,
|
||||
payload: ToMinionPayload {
|
||||
job_id: rand::random::<u64>(),
|
||||
detail: ToMinionPayloadDetail::PostEvent(Box::new(event.clone())),
|
||||
},
|
||||
}],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn clear_mute_list(&mut self) -> Result<(), Error> {
|
||||
GLOBALS.people.clear_mute_list()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn push_metadata(&mut self, metadata: Metadata) -> Result<(), Error> {
|
||||
let public_key = match GLOBALS.signer.public_key() {
|
||||
Some(pk) => pk,
|
||||
@ -1963,6 +2008,53 @@ impl Overlord {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This updates the actual mute list from the last MuteList received
|
||||
async fn update_mute_list(&mut self, merge: bool) -> Result<(), Error> {
|
||||
// Load the latest MuteList from the database
|
||||
let our_mute_list = {
|
||||
let pubkey = match GLOBALS.signer.public_key() {
|
||||
Some(pk) => pk,
|
||||
None => return Ok(()), // we cannot do anything without an identity setup first
|
||||
};
|
||||
|
||||
if let Some(event) = GLOBALS
|
||||
.storage
|
||||
.get_replaceable_event(pubkey, EventKind::MuteList)?
|
||||
{
|
||||
event.clone()
|
||||
} else {
|
||||
return Ok(()); // we have no mute list to update from
|
||||
}
|
||||
};
|
||||
|
||||
let mut pubkeys: Vec<PublicKey> = Vec::new();
|
||||
|
||||
// 'p' tags represent the author's mutes
|
||||
for tag in &our_mute_list.tags {
|
||||
if let Tag::Pubkey { pubkey, .. } = tag {
|
||||
if let Ok(pubkey) = PublicKey::try_from_hex_string(pubkey, true) {
|
||||
// Save the pubkey
|
||||
pubkeys.push(pubkey.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mute all those pubkeys, and unmute everbody else if merge=false
|
||||
GLOBALS.people.mute_all(&pubkeys, merge)?;
|
||||
|
||||
// Update last_must_list_edit
|
||||
let last_edit = if merge {
|
||||
Unixtime::now().unwrap() // now, since superior to the last event
|
||||
} else {
|
||||
our_mute_list.created_at
|
||||
};
|
||||
GLOBALS
|
||||
.storage
|
||||
.write_last_mute_list_edit(last_edit.0, None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn search(mut text: String) -> Result<(), Error> {
|
||||
if text.len() < 2 {
|
||||
GLOBALS
|
||||
|
133
src/people.rs
133
src/people.rs
@ -77,6 +77,12 @@ pub struct People {
|
||||
|
||||
// Size of the last self-owned contact list we have an event for
|
||||
pub last_contact_list_size: AtomicUsize,
|
||||
|
||||
// Date of the last self-owned mute list we have an event for
|
||||
pub last_mute_list_asof: AtomicI64,
|
||||
|
||||
// Size of the last self-owned mute list we have an event for
|
||||
pub last_mute_list_size: AtomicUsize,
|
||||
}
|
||||
|
||||
impl People {
|
||||
@ -91,14 +97,16 @@ impl People {
|
||||
tried_metadata: DashSet::new(),
|
||||
last_contact_list_asof: AtomicI64::new(0),
|
||||
last_contact_list_size: AtomicUsize::new(0),
|
||||
last_mute_list_asof: AtomicI64::new(0),
|
||||
last_mute_list_size: AtomicUsize::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
// Start the periodic task management
|
||||
pub fn start() {
|
||||
// Load our contact list from the database in order to populate
|
||||
// last_contact_list_asof and last_contact_list_size
|
||||
if let Some(pk) = GLOBALS.signer.public_key() {
|
||||
// Load our contact list from the database in order to populate
|
||||
// last_contact_list_asof and last_contact_list_size
|
||||
if let Ok(Some(event)) = GLOBALS
|
||||
.storage
|
||||
.get_replaceable_event(pk, EventKind::ContactList)
|
||||
@ -124,6 +132,29 @@ impl People {
|
||||
.store(size, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
// Load our mute list from the database in order to populate
|
||||
// last_mute_list_asof and last_mute_list_size
|
||||
if let Ok(Some(event)) = GLOBALS
|
||||
.storage
|
||||
.get_replaceable_event(pk, EventKind::MuteList)
|
||||
{
|
||||
if event.created_at.0 > GLOBALS.people.last_mute_list_asof.load(Ordering::Relaxed) {
|
||||
GLOBALS
|
||||
.people
|
||||
.last_mute_list_asof
|
||||
.store(event.created_at.0, Ordering::Relaxed);
|
||||
let size = event
|
||||
.tags
|
||||
.iter()
|
||||
.filter(|t| matches!(t, Tag::Pubkey { .. }))
|
||||
.count();
|
||||
GLOBALS
|
||||
.people
|
||||
.last_mute_list_size
|
||||
.store(size, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task::spawn(async {
|
||||
@ -602,6 +633,49 @@ impl People {
|
||||
GLOBALS.signer.sign_preevent(pre_event, None, None)
|
||||
}
|
||||
|
||||
pub async fn generate_mute_list_event(&self) -> Result<Event, Error> {
|
||||
let mut p_tags: Vec<Tag> = Vec::new();
|
||||
|
||||
let muted_pubkeys = self.get_muted_pubkeys();
|
||||
|
||||
for muted_pubkey in &muted_pubkeys {
|
||||
p_tags.push(Tag::Pubkey {
|
||||
pubkey: (*muted_pubkey).into(),
|
||||
recommended_relay_url: None,
|
||||
petname: None,
|
||||
trailing: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
let public_key = match GLOBALS.signer.public_key() {
|
||||
Some(pk) => pk,
|
||||
None => return Err((ErrorKind::NoPrivateKey, file!(), line!()).into()), // not even a public key
|
||||
};
|
||||
|
||||
// Get the content from our latest MuteList.
|
||||
// We don't use the data, but we shouldn't clobber it (it is for private mutes
|
||||
// that we have not implemented yet)
|
||||
|
||||
let content = match GLOBALS
|
||||
.storage
|
||||
.get_replaceable_event(public_key, EventKind::MuteList)?
|
||||
{
|
||||
Some(c) => c.content,
|
||||
None => "".to_owned(),
|
||||
};
|
||||
|
||||
let pre_event = PreEvent {
|
||||
pubkey: public_key,
|
||||
created_at: Unixtime::now().unwrap(),
|
||||
kind: EventKind::MuteList,
|
||||
tags: p_tags,
|
||||
content,
|
||||
ots: None,
|
||||
};
|
||||
|
||||
GLOBALS.signer.sign_preevent(pre_event, None, None)
|
||||
}
|
||||
|
||||
pub fn follow(&self, pubkey: &PublicKey, follow: bool) -> Result<(), Error> {
|
||||
let mut txn = GLOBALS.storage.get_write_txn()?;
|
||||
|
||||
@ -674,7 +748,26 @@ impl People {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn clear_mute_list(&self) -> Result<(), Error> {
|
||||
let mut txn = GLOBALS.storage.get_write_txn()?;
|
||||
|
||||
GLOBALS
|
||||
.storage
|
||||
.clear_person_list(PersonList::Muted, Some(&mut txn))?;
|
||||
GLOBALS
|
||||
.storage
|
||||
.write_last_mute_list_edit(Unixtime::now().unwrap().0, Some(&mut txn))?;
|
||||
|
||||
txn.commit()?;
|
||||
|
||||
GLOBALS.ui_invalidate_all.store(false, Ordering::Relaxed);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mute(&self, pubkey: &PublicKey, mute: bool) -> Result<(), Error> {
|
||||
let mut txn = GLOBALS.storage.get_write_txn()?;
|
||||
|
||||
if mute {
|
||||
if let Some(pk) = GLOBALS.signer.public_key() {
|
||||
if pk == *pubkey {
|
||||
@ -684,17 +777,49 @@ impl People {
|
||||
|
||||
GLOBALS
|
||||
.storage
|
||||
.add_person_to_list(pubkey, PersonList::Muted, None)?;
|
||||
.add_person_to_list(pubkey, PersonList::Muted, Some(&mut txn))?;
|
||||
} else {
|
||||
GLOBALS
|
||||
.storage
|
||||
.remove_person_from_list(pubkey, PersonList::Muted, None)?;
|
||||
.remove_person_from_list(pubkey, PersonList::Muted, Some(&mut txn))?;
|
||||
}
|
||||
|
||||
GLOBALS
|
||||
.storage
|
||||
.write_last_mute_list_edit(Unixtime::now().unwrap().0, Some(&mut txn))?;
|
||||
|
||||
txn.commit()?;
|
||||
|
||||
GLOBALS.ui_people_to_invalidate.write().push(*pubkey);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mute_all(&self, pubkeys: &[PublicKey], merge: bool) -> Result<(), Error> {
|
||||
let mut txn = GLOBALS.storage.get_write_txn()?;
|
||||
|
||||
if !merge {
|
||||
GLOBALS
|
||||
.storage
|
||||
.clear_person_list(PersonList::Muted, Some(&mut txn))?;
|
||||
}
|
||||
|
||||
for pubkey in pubkeys {
|
||||
GLOBALS
|
||||
.storage
|
||||
.add_person_to_list(pubkey, PersonList::Muted, Some(&mut txn))?;
|
||||
GLOBALS.ui_people_to_invalidate.write().push(*pubkey);
|
||||
}
|
||||
|
||||
GLOBALS
|
||||
.storage
|
||||
.write_last_mute_list_edit(Unixtime::now().unwrap().0, Some(&mut txn))?;
|
||||
|
||||
txn.commit()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Returns true if the date passed in is newer than what we already had
|
||||
pub async fn update_relay_list_stamps(
|
||||
&self,
|
||||
|
@ -234,14 +234,36 @@ pub async fn process_new_event(
|
||||
} else {
|
||||
process_somebody_elses_contact_list(event).await?;
|
||||
}
|
||||
}
|
||||
|
||||
if event.kind == EventKind::RelayList {
|
||||
} else if event.kind == EventKind::MuteList {
|
||||
if let Some(pubkey) = GLOBALS.signer.public_key() {
|
||||
if event.pubkey == pubkey {
|
||||
// We do not process our own mute list automatically.
|
||||
// Instead we only process it on user command.
|
||||
// See Overlord::update_muted()
|
||||
//
|
||||
// But we do update people.last_mute_list_asof and _size
|
||||
if event.created_at.0 > GLOBALS.people.last_mute_list_asof.load(Ordering::Relaxed) {
|
||||
GLOBALS
|
||||
.people
|
||||
.last_mute_list_asof
|
||||
.store(event.created_at.0, Ordering::Relaxed);
|
||||
let size = event
|
||||
.tags
|
||||
.iter()
|
||||
.filter(|t| matches!(t, Tag::Pubkey { .. }))
|
||||
.count();
|
||||
GLOBALS
|
||||
.people
|
||||
.last_mute_list_size
|
||||
.store(size, Ordering::Relaxed);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
} else if event.kind == EventKind::RelayList {
|
||||
GLOBALS.storage.process_relay_list(event)?;
|
||||
}
|
||||
|
||||
// If the content is a repost, seek the event it reposts
|
||||
if event.kind == EventKind::Repost {
|
||||
} else if event.kind == EventKind::Repost {
|
||||
// If the content is a repost, seek the event it reposts
|
||||
for (id, optrelay) in event.mentions().iter() {
|
||||
if let Some(rurl) = optrelay {
|
||||
let _ = GLOBALS
|
||||
|
@ -544,6 +544,44 @@ impl Storage {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_last_mute_list_edit<'a>(
|
||||
&'a self,
|
||||
when: i64,
|
||||
rw_txn: Option<&mut RwTxn<'a>>,
|
||||
) -> Result<(), Error> {
|
||||
let bytes = when.to_be_bytes();
|
||||
|
||||
let f = |txn: &mut RwTxn<'a>| -> Result<(), Error> {
|
||||
self.general
|
||||
.put(txn, b"last_mute_list_edit", bytes.as_slice())?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
match rw_txn {
|
||||
Some(txn) => f(txn)?,
|
||||
None => {
|
||||
let mut txn = self.env.write_txn()?;
|
||||
f(&mut txn)?;
|
||||
txn.commit()?;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_last_mute_list_edit(&self) -> Result<i64, Error> {
|
||||
let txn = self.env.read_txn()?;
|
||||
|
||||
match self.general.get(&txn, b"last_mute_list_edit")? {
|
||||
None => {
|
||||
let now = Unixtime::now().unwrap();
|
||||
self.write_last_mute_list_edit(now.0, None)?;
|
||||
Ok(now.0)
|
||||
}
|
||||
Some(bytes) => Ok(i64::from_be_bytes(bytes[..8].try_into().unwrap())),
|
||||
}
|
||||
}
|
||||
|
||||
// Settings ----------------------------------------------------------
|
||||
|
||||
// This defines functions for read_{setting} and write_{setting} for each
|
||||
|
@ -387,6 +387,7 @@ struct GossipUi {
|
||||
follow_pubkey: String,
|
||||
follow_pubkey_at_relay: String,
|
||||
follow_clear_needs_confirm: bool,
|
||||
mute_clear_needs_confirm: bool,
|
||||
password: String,
|
||||
password2: String,
|
||||
password3: String,
|
||||
@ -598,6 +599,7 @@ impl GossipUi {
|
||||
follow_pubkey: "".to_owned(),
|
||||
follow_pubkey_at_relay: "".to_owned(),
|
||||
follow_clear_needs_confirm: false,
|
||||
mute_clear_needs_confirm: false,
|
||||
password: "".to_owned(),
|
||||
password2: "".to_owned(),
|
||||
password3: "".to_owned(),
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::{GossipUi, Page};
|
||||
use crate::comms::ToOverlordMessage;
|
||||
use crate::globals::GLOBALS;
|
||||
use crate::people::Person;
|
||||
use crate::AVATAR_SIZE_F32;
|
||||
@ -7,8 +8,6 @@ use egui::{Context, Image, RichText, ScrollArea, Sense, Ui, Vec2};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
|
||||
ui.add_space(30.0);
|
||||
|
||||
let muted_pubkeys = GLOBALS.people.get_muted_pubkeys();
|
||||
let mut people: Vec<Person> = Vec::new();
|
||||
for pk in &muted_pubkeys {
|
||||
@ -18,6 +17,109 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
|
||||
}
|
||||
people.sort_unstable();
|
||||
|
||||
ui.add_space(12.0);
|
||||
|
||||
let last_mute_list_size = GLOBALS.people.last_mute_list_size.load(Ordering::Relaxed);
|
||||
let last_mute_list_asof = GLOBALS.people.last_mute_list_asof.load(Ordering::Relaxed);
|
||||
let mut asof = "unknown".to_owned();
|
||||
if let Ok(stamp) = time::OffsetDateTime::from_unix_timestamp(last_mute_list_asof) {
|
||||
if let Ok(formatted) = stamp.format(time::macros::format_description!(
|
||||
"[year]-[month repr:short]-[day] ([weekday repr:short]) [hour]:[minute]"
|
||||
)) {
|
||||
asof = formatted;
|
||||
}
|
||||
}
|
||||
|
||||
ui.label(RichText::new(format!("REMOTE: {} (size={})", asof, last_mute_list_size)).size(15.0))
|
||||
.on_hover_text("This is the data in the latest MuteList event fetched from relays");
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(30.0);
|
||||
|
||||
if ui
|
||||
.button("↓ Overwrite ↓")
|
||||
.on_hover_text("This pulls down your Mute List, erasing anything that is already here")
|
||||
.clicked()
|
||||
{
|
||||
let _ = GLOBALS
|
||||
.to_overlord
|
||||
.send(ToOverlordMessage::UpdateMuteList(false));
|
||||
}
|
||||
if ui
|
||||
.button("↓ Merge ↓")
|
||||
.on_hover_text("This pulls down your Mute List, merging it into what is already here")
|
||||
.clicked()
|
||||
{
|
||||
let _ = GLOBALS
|
||||
.to_overlord
|
||||
.send(ToOverlordMessage::UpdateMuteList(true));
|
||||
}
|
||||
|
||||
if GLOBALS.signer.is_ready() {
|
||||
if ui
|
||||
.button("↑ Publish ↑")
|
||||
.on_hover_text("This publishes your Mute List")
|
||||
.clicked()
|
||||
{
|
||||
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::PushMuteList);
|
||||
}
|
||||
}
|
||||
|
||||
if GLOBALS.signer.is_ready() {
|
||||
if app.mute_clear_needs_confirm {
|
||||
if ui.button("CANCEL").clicked() {
|
||||
app.mute_clear_needs_confirm = false;
|
||||
}
|
||||
if ui.button("YES, CLEAR ALL").clicked() {
|
||||
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::ClearMuteList);
|
||||
app.mute_clear_needs_confirm = false;
|
||||
}
|
||||
} else {
|
||||
if ui.button("Clear All").clicked() {
|
||||
app.mute_clear_needs_confirm = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
let last_mute_list_edit = match GLOBALS.storage.read_last_mute_list_edit() {
|
||||
Ok(date) => date,
|
||||
Err(e) => {
|
||||
tracing::error!("{}", e);
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
let mut ledit = "unknown".to_owned();
|
||||
if let Ok(stamp) = time::OffsetDateTime::from_unix_timestamp(last_mute_list_edit) {
|
||||
if let Ok(formatted) = stamp.format(time::macros::format_description!(
|
||||
"[year]-[month repr:short]-[day] ([weekday repr:short]) [hour]:[minute]"
|
||||
)) {
|
||||
ledit = formatted;
|
||||
}
|
||||
}
|
||||
ui.label(RichText::new(format!("LOCAL: {} (size={})", ledit, people.len())).size(15.0))
|
||||
.on_hover_text("This is the local (and effective) mute list");
|
||||
|
||||
if !GLOBALS.signer.is_ready() {
|
||||
ui.add_space(10.0);
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label("You need to ");
|
||||
if ui.link("setup your identity").clicked() {
|
||||
app.set_page(Page::YourKeys);
|
||||
}
|
||||
ui.label(" to push.");
|
||||
});
|
||||
}
|
||||
|
||||
ui.add_space(10.0);
|
||||
ui.separator();
|
||||
ui.add_space(10.0);
|
||||
|
||||
ui.heading(format!("People who are Muted ({})", people.len()));
|
||||
ui.add_space(10.0);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user