## More options in the Examine → Options menus
-### Your own options: ESC Y O
+### Your own options: \[ESC\]\[Y\]\[O\]
-If the user is you, then this menu sets client configuration. For
-example, the default language for your posts (if you want to override
-the one we get from the system locale). Maybe also UI options in
-future if we add any.
+Still to do, and harder UI-wise than the rest of the user options:
-It should also let you [set server-side
-options](https://docs.joinmastodon.org/methods/accounts/#update_credentials)
-about your account, such as your display name, flags like `bot`, and
-info fields.
+* setting the account's bio: involves spawning a full-screen editor
+ activity
+* setting the account's "fields" (key-value pairs): involves managing
+ a variable-length set of pairs, and either dynamically choosing menu
+ keystrokes for them all, or having a new kind of activity consisting
+ of a sequence of single-line editors that you can cursor up and down
+ to select one to edit.
+
+Maybe also client-side UI options in future, if we add any.
### Options for other users
-I think we've got all the sensible server-side options about other
-users (except _maybe_ getting a notification when they post, mentioned
-separately below).
+I think we've got all the usual server-side options about other users.
+Getting a notification when they post is mentioned separately below.
+Perhaps the only missing one is forcibly stopping a user from
+following _you_.
But I wonder if it might be worth having client-side options for
users. For example, perhaps the client could usefully keep a list of
### Locked accounts
You can lock your account so that people can only follow you if you
-give permission. We should support that:
+give permission. We already support actually _doing_ this -- it's one
+of the options in the \[ESC\]\[Y\]\[O\] options menu. But there's more
+to do to make sure this works well:
-* support [marking your own account as
- locked](https://docs.joinmastodon.org/methods/accounts/#update_credentials)
- via the ESC Y O options menu
* put follow requests into some kind of notifications feed that causes
you to hear about them
* allow you to [get a list of your current pending
that. On client startup, check it for unread ones, and display those
in the style of real Monochrome's MOTD.
+# Future directions
+
+## UI configuration options
+
+Some of the keypaths to features of this client are very strange if
+you're _not_ already used to the Mono UI. Perhaps there should be an
+alternative set of keypaths that make more sense. This may need to be
+a configurable option (or several).
+
+Not everyone will want their notifications feed broken up the way we
+do it here. That should be an option as well, in some fashion.
+
## Archive support
The Mastodon web UI lets you download an archive of all your own posts
-use super::client::{Client, ClientError, Boosts, Followness, AccountFlag};
+use super::client::{
+ Client, ClientError, Boosts, Followness, AccountFlag, AccountDetails
+};
use super::coloured_string::ColouredString;
use super::tui::{
ActivityState, CursorPosition, LogicalAction, OurKey, OurKey::*,
};
use super::text::*;
+use super::types::Visibility;
use super::editor::{EditableMenuLine, EditableMenuLineData};
struct YourOptionsMenu {
title: FileHeader,
- coming_soon: CentredInfoLine,
normal_status: FileStatusLineFinal,
edit_status: FileStatusLineFinal,
+ el_display_name: EditableMenuLine<String>, // N
+ cl_default_vis: CyclingMenuLine<Visibility>,
+ cl_default_sensitive: CyclingMenuLine<bool>,
+ el_default_language: EditableMenuLine<Option<String>>,
+
+ cl_locked: CyclingMenuLine<bool>,
+ cl_bot: CyclingMenuLine<bool>,
+ cl_discoverable: CyclingMenuLine<bool>,
+ cl_hide_collections: CyclingMenuLine<bool>,
+ cl_indexable: CyclingMenuLine<bool>,
+
+ // fields (harder because potentially open-ended number of them)
+ // note (bio) (harder because flip to an editor)
}
impl YourOptionsMenu {
- fn new() -> Self {
+ fn new(client: &mut Client) -> Result<Self, ClientError> {
+ let ac = client.account_by_id(&client.our_account_id())?;
+ let source = match ac.source {
+ Some(source) => source,
+ None => return Err(ClientError::NoAccountSource),
+ };
+
let title = FileHeader::new(ColouredString::general(
"Your user options [ESC][Y][O]",
"HHHHHHHHHHHHHHHHHHHKKKHHKHHKH"));
let edit_status = FileStatusLine::new()
.message("Edit line and press Return").finalise();
- let coming_soon = CentredInfoLine::new(
- ColouredString::uniform("Not yet implemented", '!'));
+ let el_display_name = EditableMenuLine::new(
+ Pr('N'), ColouredString::plain("Display name: "),
+ ac.display_name.clone());
+ let cl_default_vis = CyclingMenuLine::new(
+ Pr('V'), ColouredString::plain("Default post visibility: "),
+ &Visibility::long_descriptions(), source.privacy);
+ let el_default_language = EditableMenuLine::new(
+ Pr('L'), ColouredString::plain("Default language: "),
+ source.language.clone());
+ let cl_default_sensitive = CyclingMenuLine::new(
+ Pr('S'),
+ ColouredString::plain("Posts marked sensitive by default: "),
+ &[(false, ColouredString::plain("no")),
+ (true, ColouredString::uniform("yes", 'r'))], source.sensitive);
+ let cl_locked = CyclingMenuLine::new(
+ Ctrl('K'),
+ ColouredString::plain("Locked (you must approve followers): "),
+ &[(false, ColouredString::plain("no")),
+ (true, ColouredString::uniform("yes", 'r'))], ac.locked);
+ let cl_hide_collections = CyclingMenuLine::new(
+ Ctrl('F'),
+ ColouredString::plain("Hide your lists of followers and followed users: "),
+ &[(false, ColouredString::plain("no")),
+ (true, ColouredString::uniform("yes", 'r'))],
+ source.hide_collections == Some(true));
+ let cl_discoverable = CyclingMenuLine::new(
+ Ctrl('D'),
+ ColouredString::plain("Discoverable (listed in profile directory): "),
+ &[(false, ColouredString::uniform("no", 'r')),
+ (true, ColouredString::uniform("yes", 'f'))],
+ source.discoverable == Some(true));
+ let cl_indexable = CyclingMenuLine::new(
+ Ctrl('X'),
+ ColouredString::plain("Indexable (people can search for your posts): "),
+ &[(false, ColouredString::uniform("no", 'r')),
+ (true, ColouredString::uniform("yes", 'f'))],
+ source.indexable == Some(true));
+ let cl_bot = CyclingMenuLine::new(
+ Ctrl('B'),
+ ColouredString::plain("Bot (account identifies as automated): "),
+ &[(false, ColouredString::uniform("no", 'f')),
+ (true, ColouredString::uniform("yes", 'H'))], ac.bot);
let mut menu = YourOptionsMenu {
title,
- coming_soon,
normal_status,
edit_status,
+ el_display_name,
+ cl_default_vis,
+ cl_default_sensitive,
+ el_default_language,
+ cl_locked,
+ cl_bot,
+ cl_discoverable,
+ cl_hide_collections,
+ cl_indexable,
};
menu.fix_widths();
- menu
+ Ok(menu)
}
fn fix_widths(&mut self) -> (usize, usize) {
- let /* mut */ lmaxwid = 0;
- let /* mut */ rmaxwid = 0;
- // self.cl_WHATEVER.check_widths(&mut lmaxwid, &mut rmaxwid);
+ let mut lmaxwid = 0;
+ let mut rmaxwid = 0;
+ self.el_display_name.check_widths(&mut lmaxwid, &mut rmaxwid);
+ self.cl_default_vis.check_widths(&mut lmaxwid, &mut rmaxwid);
+ self.el_default_language.check_widths(&mut lmaxwid, &mut rmaxwid);
+ self.cl_default_sensitive.check_widths(&mut lmaxwid, &mut rmaxwid);
+ self.cl_locked.check_widths(&mut lmaxwid, &mut rmaxwid);
+ self.cl_bot.check_widths(&mut lmaxwid, &mut rmaxwid);
+ self.cl_discoverable.check_widths(&mut lmaxwid, &mut rmaxwid);
+ self.cl_hide_collections.check_widths(&mut lmaxwid, &mut rmaxwid);
+ self.cl_indexable.check_widths(&mut lmaxwid, &mut rmaxwid);
+
+ self.el_display_name.reset_widths();
+ self.cl_default_vis.reset_widths();
+ self.el_default_language.reset_widths();
+ self.cl_default_sensitive.reset_widths();
+ self.cl_locked.reset_widths();
+ self.cl_bot.reset_widths();
+ self.cl_discoverable.reset_widths();
+ self.cl_hide_collections.reset_widths();
+ self.cl_indexable.reset_widths();
+
+ self.el_display_name.ensure_widths(lmaxwid, rmaxwid);
+ self.cl_default_vis.ensure_widths(lmaxwid, rmaxwid);
+ self.el_default_language.ensure_widths(lmaxwid, rmaxwid);
+ self.cl_default_sensitive.ensure_widths(lmaxwid, rmaxwid);
+ self.cl_locked.ensure_widths(lmaxwid, rmaxwid);
+ self.cl_bot.ensure_widths(lmaxwid, rmaxwid);
+ self.cl_discoverable.ensure_widths(lmaxwid, rmaxwid);
+ self.cl_hide_collections.ensure_widths(lmaxwid, rmaxwid);
+ self.cl_indexable.ensure_widths(lmaxwid, rmaxwid);
+
+ (lmaxwid, rmaxwid)
+ }
- // self.cl_WHATEVER.reset_widths();
- // self.cl_WHATEVER.ensure_widths(lmaxwid, rmaxwid);
+ fn submit(&self, client: &mut Client) -> LogicalAction {
+ let details = AccountDetails {
+ display_name: self.el_display_name.get_data().clone(),
+ default_visibility: self.cl_default_vis.get_value(),
+ default_sensitive: self.cl_default_sensitive.get_value(),
+ default_language: self.el_default_language.get_data().clone(),
+ locked: self.cl_locked.get_value(),
+ bot: self.cl_bot.get_value(),
+ discoverable: self.cl_discoverable.get_value(),
+ hide_collections: self.cl_hide_collections.get_value(),
+ indexable: self.cl_indexable.get_value(),
+ };
- (lmaxwid, rmaxwid)
+ match client.set_account_details(&client.our_account_id(), details) {
+ Ok(..) => LogicalAction::Pop,
+ Err(..) => LogicalAction::Beep, // FIXME: report the error!
+ }
}
}
fn draw(&self, w: usize, h: usize)
-> (Vec<ColouredString>, CursorPosition) {
let mut lines = Vec::new();
- let /* mut */ cursorpos = CursorPosition::End;
+ let mut cursorpos = CursorPosition::End;
lines.extend_from_slice(&self.title.render(w));
lines.extend_from_slice(&BlankLine::render_static());
- lines.extend_from_slice(&self.coming_soon.render(w));
- // FIXME menu items
+ lines.push(self.el_display_name.render(
+ w, &mut cursorpos, lines.len()));
+ lines.extend_from_slice(&BlankLine::render_static());
+ lines.extend_from_slice(&self.cl_default_vis.render(w));
+ lines.push(self.el_default_language.render(
+ w, &mut cursorpos, lines.len()));
+ lines.extend_from_slice(&self.cl_default_sensitive.render(w));
+ lines.extend_from_slice(&BlankLine::render_static());
+ lines.extend_from_slice(&self.cl_locked.render(w));
+ lines.extend_from_slice(&self.cl_hide_collections.render(w));
+ lines.extend_from_slice(&self.cl_discoverable.render(w));
+ lines.extend_from_slice(&self.cl_indexable.render(w));
+ lines.extend_from_slice(&BlankLine::render_static());
+ lines.extend_from_slice(&self.cl_bot.render(w));
while lines.len() + 1 < h {
lines.extend_from_slice(&BlankLine::render_static());
}
- if false /* self.el_WHATEVER.is_editing() */ {
+ if self.el_display_name.is_editing() ||
+ self.el_default_language.is_editing(){
lines.extend_from_slice(&self.edit_status.render(w));
} else {
lines.extend_from_slice(&self.normal_status.render(w));
(lines, cursorpos)
}
- fn handle_keypress(&mut self, key: OurKey, _client: &mut Client) ->
+ fn handle_keypress(&mut self, key: OurKey, client: &mut Client) ->
LogicalAction
{
// Let editable menu lines have first crack at the keypress
- if false /* self.el_WHATEVER.handle_keypress(key) */
+ if self.el_display_name.handle_keypress(key) ||
+ self.el_default_language.handle_keypress(key)
{
self.fix_widths();
return LogicalAction::Nothing;
}
match key {
+ Space => self.submit(client),
Pr('q') | Pr('Q') => LogicalAction::Pop,
+ Pr('n') | Pr('N') => self.el_display_name.start_editing(),
+ Pr('v') | Pr('V') => self.cl_default_vis.cycle(),
+ Pr('l') | Pr('L') => self.el_default_language.start_editing(),
+ Pr('s') | Pr('S') => self.cl_default_sensitive.cycle(),
+ Ctrl('K') => self.cl_locked.cycle(),
+ Ctrl('F') => self.cl_hide_collections.cycle(),
+ Ctrl('D') => self.cl_discoverable.cycle(),
+ Ctrl('X') => self.cl_indexable.cycle(),
+ Ctrl('B') => self.cl_bot.cycle(),
_ => LogicalAction::Nothing,
}
}
- fn resize(&mut self, _w: usize, _h: usize) {
- /*
- self.el_WHATEVER.resize(w);
- */
+ fn resize(&mut self, w: usize, _h: usize) {
+ self.el_display_name.resize(w);
+ self.el_default_language.resize(w);
}
}
-> Result<Box<dyn ActivityState>, ClientError>
{
if id == client.our_account_id() {
- Ok(Box::new(YourOptionsMenu::new()))
+ Ok(Box::new(YourOptionsMenu::new(client)?))
} else {
Ok(Box::new(OtherUserOptionsMenu::new(client, id)?))
}