mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-24 10:27:00 -06:00
#338: drop the start/end feat marker comments
This commit is contained in:
@@ -1489,10 +1489,8 @@ impl Backend for Store {
|
||||
Admin::Blocked => AdminReply::Blocked(self.blocked()).into(),
|
||||
Admin::Code { file, token } => self.set_code(&file, &token).map_or_else(failed, |code| AdminReply::Code { code }.into()),
|
||||
Admin::Owned { files } => self.owned(&files).into(),
|
||||
// start feat: remove-from-session
|
||||
// The hub removes people itself (it knows who is connected); it never asks the store.
|
||||
Admin::RemovePeer { .. } => failed("removing someone from a file is done by the server's hub".into()),
|
||||
// end feat: remove-from-session
|
||||
Admin::Remove { file } => {
|
||||
if !self.files.contains_key(&file) {
|
||||
return failed("this file isn't on the server (it may have been removed)".into());
|
||||
|
||||
@@ -11,12 +11,10 @@ use figcraft_collab::{Outgoing, Server, same_secret};
|
||||
use figcraft_doc::Document;
|
||||
use figcraft_engine::live::{Event, PEOPLE};
|
||||
|
||||
pub use crate::hub::REMOVED;
|
||||
use crate::hub::{Admission, Backend, Closing, Hosting, Local, clean_name, to_local};
|
||||
use crate::inbox::Inbox;
|
||||
pub use crate::join::ENDED;
|
||||
// start feat: remove-from-session
|
||||
pub use crate::hub::REMOVED;
|
||||
// end feat: remove-from-session
|
||||
use crate::net::NetLimits;
|
||||
|
||||
/// What a hosted session needs.
|
||||
@@ -132,13 +130,11 @@ impl Host {
|
||||
self.hosting.send(msg);
|
||||
}
|
||||
|
||||
// start feat: remove-from-session
|
||||
/// Remove client `id` from the session: they are disconnected and can't join again from
|
||||
/// their address while it runs. The host can't remove themselves.
|
||||
pub fn remove(&self, id: u32) {
|
||||
self.hosting.remove(id);
|
||||
}
|
||||
// end feat: remove-from-session
|
||||
|
||||
/// Cut every connection as a network failure would (no close frame).
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -413,10 +413,8 @@ enum HubIn {
|
||||
},
|
||||
Local(Presence),
|
||||
LocalMsg(ClientMsg),
|
||||
// start feat: remove-from-session
|
||||
/// The person hosting removes client `id` from their file (app sessions).
|
||||
Remove(u32),
|
||||
// end feat: remove-from-session
|
||||
/// Cut every connection without a word (tests: a network failure).
|
||||
#[cfg(test)]
|
||||
Cut,
|
||||
@@ -563,13 +561,11 @@ impl Hosting {
|
||||
let _ = self.hub.send(HubIn::LocalMsg(msg));
|
||||
}
|
||||
|
||||
// start feat: remove-from-session
|
||||
/// The hosting person removes client `id` from their file (app sessions; see
|
||||
/// [`HubIn::Remove`]).
|
||||
pub(crate) fn remove(&self, id: u32) {
|
||||
let _ = self.hub.send(HubIn::Remove(id));
|
||||
}
|
||||
// end feat: remove-from-session
|
||||
|
||||
/// Cut every connection as a network failure would (no close frame).
|
||||
#[cfg(test)]
|
||||
@@ -1128,7 +1124,6 @@ fn unsupported(protocol: u32) -> String {
|
||||
/// sharing): the session's end for them.
|
||||
pub const STOPPED: &str = "Sharing was stopped: the link you joined with no longer works. Your copy stays open";
|
||||
|
||||
// start feat: remove-from-session
|
||||
/// What someone removed from a file is told: the session's end for them (they keep their copy;
|
||||
/// nothing retries).
|
||||
pub const REMOVED: &str = "You were removed from this session. Your copy stays open";
|
||||
@@ -1138,7 +1133,6 @@ const BARRED: &str = "you were removed from this session";
|
||||
|
||||
/// The most addresses one file keeps barred.
|
||||
const MAX_BARRED: usize = 256;
|
||||
// end feat: remove-from-session
|
||||
|
||||
/// What everyone in a file is told when its changes couldn't be saved (they reconnect).
|
||||
const UNSAVED: &str = "The server couldn't save the latest changes; reconnecting";
|
||||
@@ -1153,10 +1147,8 @@ struct Hub<B> {
|
||||
local: Option<String>,
|
||||
/// What the servers said, sent once the backend made what changed durable.
|
||||
outbox: Vec<(String, Outgoing)>,
|
||||
// start feat: remove-from-session
|
||||
/// Addresses ([`ip_key`]) removed from each file: they can't join it again while the hub runs.
|
||||
barred: HashMap<String, HashSet<IpAddr>>,
|
||||
// end feat: remove-from-session
|
||||
}
|
||||
|
||||
impl<B: Backend> Hub<B> {
|
||||
@@ -1244,7 +1236,6 @@ impl<B: Backend> Hub<B> {
|
||||
self.client_message(&file, id, msg, None);
|
||||
}
|
||||
}
|
||||
// start feat: remove-from-session
|
||||
HubIn::Remove(id) => {
|
||||
if let Some(file) = self.local.clone()
|
||||
&& let Err(why) = self.remove_peer(&file, id)
|
||||
@@ -1252,7 +1243,6 @@ impl<B: Backend> Hub<B> {
|
||||
log::info!("live session: couldn't remove client {id}: {why}");
|
||||
}
|
||||
}
|
||||
// end feat: remove-from-session
|
||||
#[cfg(test)]
|
||||
HubIn::Cut => {
|
||||
for c in self.conns.values() {
|
||||
@@ -1277,7 +1267,6 @@ impl<B: Backend> Hub<B> {
|
||||
self.client_message(&file, id, msg, Some(bytes));
|
||||
}
|
||||
Some(Kind::Hosting { owns }) => match msg {
|
||||
// start feat: remove-from-session
|
||||
ClientMsg::Admin(Admin::RemovePeer { file, client }) => {
|
||||
let reply = if owns.as_deref() == Some(file.as_str()) {
|
||||
self.removed_reply(&file, client)
|
||||
@@ -1286,7 +1275,6 @@ impl<B: Backend> Hub<B> {
|
||||
};
|
||||
self.answer(conn, &ServerMsg::Admin(Box::new(reply)));
|
||||
}
|
||||
// end feat: remove-from-session
|
||||
ClientMsg::Admin(req) => {
|
||||
let owns = owns.clone();
|
||||
let rooms = &self.rooms;
|
||||
@@ -1298,12 +1286,10 @@ impl<B: Backend> Hub<B> {
|
||||
_ => self.kick(&[conn], "only publishing and managing your own files are spoken on this connection"),
|
||||
},
|
||||
Some(Kind::Managing) => match msg {
|
||||
// start feat: remove-from-session
|
||||
ClientMsg::Admin(Admin::RemovePeer { file, client }) => {
|
||||
let reply = self.removed_reply(&file, client);
|
||||
self.answer(conn, &ServerMsg::Admin(Box::new(reply)));
|
||||
}
|
||||
// end feat: remove-from-session
|
||||
ClientMsg::Admin(req) => self.admin(conn, req),
|
||||
ClientMsg::Ping => self.answer(conn, &ServerMsg::Pong),
|
||||
_ => self.kick(&[conn], "only requests to manage files are spoken on this connection"),
|
||||
@@ -1366,12 +1352,10 @@ impl<B: Backend> Hub<B> {
|
||||
Ok(a) => a,
|
||||
Err(why) => return refuse(&c.out, &why),
|
||||
};
|
||||
// start feat: remove-from-session
|
||||
if self.barred.get(&hello.file).is_some_and(|b| b.contains(&c.ip)) {
|
||||
log::info!("event=refused ip={} reason=removed", c.ip);
|
||||
return refuse(&c.out, BARRED);
|
||||
}
|
||||
// end feat: remove-from-session
|
||||
let hosting = reads_hosting(&hello.client_version);
|
||||
// The backend checked the secret: the sessions don't keep it.
|
||||
hello.token.clear();
|
||||
@@ -1429,7 +1413,6 @@ impl<B: Backend> Hub<B> {
|
||||
}
|
||||
}
|
||||
|
||||
// start feat: remove-from-session
|
||||
/// Remove client `id` from `file`: they are told the session is over for them and
|
||||
/// disconnected, and their address can't join the file again while the hub runs. The person
|
||||
/// hosting can't be removed.
|
||||
@@ -1457,7 +1440,6 @@ impl<B: Backend> Hub<B> {
|
||||
Err(reason) => AdminReply::Failed { reason },
|
||||
}
|
||||
}
|
||||
// end feat: remove-from-session
|
||||
|
||||
/// Take in connection `c` as `kind` and send it `first`.
|
||||
fn take_in(&mut self, conn: u64, c: Pending, kind: Kind, first: &ServerMsg) {
|
||||
|
||||
@@ -243,7 +243,6 @@ impl LiveLink for NetLink {
|
||||
}
|
||||
}
|
||||
|
||||
// start feat: remove-from-session
|
||||
fn remove(&mut self, session: SessionId, client: u32) -> Result<(), String> {
|
||||
match self.sessions.get(session) {
|
||||
Some(Active::Host(h)) if h.me().client_id == client => Err("you can't remove yourself: stop the session instead".into()),
|
||||
@@ -255,7 +254,6 @@ impl LiveLink for NetLink {
|
||||
None => Err("this session is over".into()),
|
||||
}
|
||||
}
|
||||
// end feat: remove-from-session
|
||||
|
||||
fn leave(&mut self, session: SessionId) {
|
||||
if let Some(active) = self.sessions.remove(session) {
|
||||
|
||||
@@ -612,7 +612,6 @@ fn until_presence_or_pong(r: &mut Raw) -> Option<ServerMsg> {
|
||||
}
|
||||
}
|
||||
|
||||
// start feat: remove-from-session
|
||||
#[test]
|
||||
fn the_host_removes_someone_and_they_cant_come_back() {
|
||||
let (mut host, mut hp, invite) = start();
|
||||
@@ -643,4 +642,3 @@ fn the_host_removes_someone_and_they_cant_come_back() {
|
||||
host.stop();
|
||||
assert_eq!(host.connections(), 0);
|
||||
}
|
||||
// end feat: remove-from-session
|
||||
|
||||
@@ -75,9 +75,7 @@ fn samples() -> (Vec<ClientMsg>, Vec<ServerMsg>) {
|
||||
ClientMsg::Admin(Admin::Block { ip: "203.0.113.9".into() }),
|
||||
ClientMsg::Admin(Admin::Unblock { ip: "2001:db8::".into() }),
|
||||
ClientMsg::Admin(Admin::Blocked),
|
||||
// start feat: remove-from-session
|
||||
ClientMsg::Admin(Admin::RemovePeer { file: "f".into(), client: 7 }),
|
||||
// end feat: remove-from-session
|
||||
ClientMsg::Report { reason: "Spam ☕".into() },
|
||||
];
|
||||
let snapshot = Replica::new(Document::new("Snap")).snapshot().unwrap();
|
||||
|
||||
@@ -274,12 +274,10 @@ pub enum Admin {
|
||||
/// these `(file id, owner token)` pairs own, at most [`MAX_OWNED`] of them:
|
||||
/// [`AdminReply::Owned`]. Servers older than this request can't read it.
|
||||
Owned { files: Vec<(String, String)> },
|
||||
// start feat: remove-from-session
|
||||
/// Remove client `client` from `file`'s session: they are disconnected and their address
|
||||
/// can't join the file again while the server runs. With the server key, or on an open
|
||||
/// hosting connection with `file`'s owner token. Servers older than this request can't read it.
|
||||
RemovePeer { file: String, client: u32 },
|
||||
// end feat: remove-from-session
|
||||
}
|
||||
|
||||
/// The most files one [`Admin::Owned`] asks about.
|
||||
|
||||
@@ -517,13 +517,11 @@ pub trait LiveLink: Send {
|
||||
fn background(&mut self, _job: u64, _req: NetRequest) -> bool {
|
||||
false
|
||||
}
|
||||
// start feat: remove-from-session
|
||||
/// Remove client `client` from session `session`, which this link hosts: they are
|
||||
/// disconnected and can't join again from their address while it runs. Returns at once.
|
||||
fn remove(&mut self, _session: SessionId, _client: u32) -> std::result::Result<(), String> {
|
||||
Err("removing people needs the desktop app or the MCP server".into())
|
||||
}
|
||||
// end feat: remove-from-session
|
||||
/// End session `session` (stop hosting or leave); the others go on. Nothing for a session
|
||||
/// that is over already.
|
||||
fn leave(&mut self, session: SessionId);
|
||||
@@ -1896,7 +1894,6 @@ fn follow(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Ok(json!({"follow": s.live().follow}))
|
||||
}
|
||||
|
||||
// start feat: remove-from-session
|
||||
/// Remove someone from the open file's session: in a session this app hosts, the hub drops them;
|
||||
/// in a file of yours on a server, the server does (with the file's owner token). They are told
|
||||
/// the session is over for them, keep their copy, and can't join again from their address while
|
||||
@@ -1949,7 +1946,6 @@ pub fn can_remove_people(s: &Session) -> bool {
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
// end feat: remove-from-session
|
||||
|
||||
fn cursor(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let at = match (num(p, "x"), num(p, "y")) {
|
||||
@@ -2059,7 +2055,6 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
|s, _| Ok(s.live_summary())
|
||||
),
|
||||
spec!("collab.follow", "Follow", "{client? (a peer's id; omit to stop following)} → {follow}", false, follow),
|
||||
// start feat: remove-from-session
|
||||
spec!(
|
||||
"collab.removePerson",
|
||||
"Remove from session",
|
||||
@@ -2067,7 +2062,6 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
false,
|
||||
remove_person
|
||||
),
|
||||
// end feat: remove-from-session
|
||||
spec!(
|
||||
"collab.cursor",
|
||||
"Show cursor at",
|
||||
|
||||
@@ -28,10 +28,8 @@ pub(crate) struct Script {
|
||||
/// The session each presence in `sent` and each message in `msgs` was for.
|
||||
pub(crate) sent_to: Vec<u64>,
|
||||
pub(crate) msgs_to: Vec<u64>,
|
||||
// start feat: remove-from-session
|
||||
/// The people removed: `(session, client)`.
|
||||
pub(crate) removed: Vec<(u64, u32)>,
|
||||
// end feat: remove-from-session
|
||||
}
|
||||
|
||||
pub(crate) struct Mock(pub(crate) Arc<Mutex<Script>>);
|
||||
@@ -66,12 +64,10 @@ impl LiveLink for Mock {
|
||||
s.published.push(opts);
|
||||
Ok(())
|
||||
}
|
||||
// start feat: remove-from-session
|
||||
fn remove(&mut self, session: u64, client: u32) -> Result<(), String> {
|
||||
self.0.lock().unwrap().removed.push((session, client));
|
||||
Ok(())
|
||||
}
|
||||
// end feat: remove-from-session
|
||||
fn leave(&mut self, session: u64) {
|
||||
let mut s = self.0.lock().unwrap();
|
||||
s.left += 1;
|
||||
@@ -324,7 +320,6 @@ fn the_host_ending_resets_everything() {
|
||||
assert_eq!(s.message.as_deref(), Some("The host ended the session"));
|
||||
}
|
||||
|
||||
// start feat: remove-from-session
|
||||
#[test]
|
||||
fn the_host_removes_people_and_guests_cant() {
|
||||
let (mut s, script) = session();
|
||||
@@ -361,7 +356,6 @@ fn the_host_removes_people_and_guests_cant() {
|
||||
assert!(e.contains("only the person hosting"), "{e}");
|
||||
assert!(script.lock().unwrap().removed.is_empty());
|
||||
}
|
||||
// end feat: remove-from-session
|
||||
|
||||
#[test]
|
||||
fn collab_commands_survive_junk() {
|
||||
|
||||
@@ -460,10 +460,8 @@ pub fn people_bar(app: &mut FigApp, ui: &mut Ui, area: Rect) {
|
||||
let peers: Vec<(u32, String, Color32)> = live.peers.values().map(|p| (p.client_id, p.name.clone(), color(&p.color))).collect();
|
||||
let follow = live.follow;
|
||||
let (in_session, spotlighting) = (live.status == Status::Live, live.spotlighting);
|
||||
// start feat: remove-from-session
|
||||
let can_remove = figcraft_engine::live::can_remove_people(&app.session);
|
||||
let mut remove = None;
|
||||
// end feat: remove-from-session
|
||||
// Room for the avatars after yours and the chevron.
|
||||
let fit = (((area.width() - 2.0 * R - 18.0) / STEP).floor() as usize).max(1);
|
||||
let (show, more) = if peers.len() > fit { (fit - 1, peers.len() - (fit - 1)) } else { (peers.len(), 0) };
|
||||
@@ -501,7 +499,6 @@ pub fn people_bar(app: &mut FigApp, ui: &mut Ui, area: Rect) {
|
||||
if resp.clicked() {
|
||||
clicked = Some(*id);
|
||||
}
|
||||
// start feat: remove-from-session
|
||||
if can_remove {
|
||||
crate::right::context_popup(&resp).show(|ui| {
|
||||
if menu_item(ui, &trf("Remove {name} from session", &[("name", name)]), "") {
|
||||
@@ -510,7 +507,6 @@ pub fn people_bar(app: &mut FigApp, ui: &mut Ui, area: Rect) {
|
||||
}
|
||||
});
|
||||
}
|
||||
// end feat: remove-from-session
|
||||
}
|
||||
if more > 0 {
|
||||
x += STEP;
|
||||
@@ -554,12 +550,10 @@ pub fn people_bar(app: &mut FigApp, ui: &mut Ui, area: Rect) {
|
||||
if spotlight {
|
||||
app.run("collab.spotlight", json!({"on": !spotlighting}));
|
||||
}
|
||||
// start feat: remove-from-session
|
||||
// A server is asked without the window waiting for it.
|
||||
if let Some(id) = remove {
|
||||
app.run_background(&ui.ctx().clone(), "collab.removePerson", json!({"client": id}), Box::new(|_, _, _| {}));
|
||||
}
|
||||
// end feat: remove-from-session
|
||||
}
|
||||
|
||||
/// The Share button's popover: the live session (start, invite link, stop or leave) and export.
|
||||
|
||||
@@ -386,7 +386,6 @@ resuming with its offline edits, and leaving the session going back to snapshot
|
||||
outlines in the person's colour, and avatars (initials) at the top of the right panel beside
|
||||
Share; click an avatar to **follow** that person's viewport (`collab.follow {client}`), click
|
||||
again, press Escape or pan/zoom yourself to stop. The chevron lists everyone.
|
||||
<!-- start feat: remove-from-session -->
|
||||
- **Removing someone.** Right-click their avatar ▸ "Remove <name> from session"
|
||||
(`collab.removePerson {client}`), when you host the session or own the file on a server. The
|
||||
hub closes their connection with "You were removed from this session" (close code 1001: the
|
||||
@@ -395,7 +394,6 @@ resuming with its offline edits, and leaving the session going back to snapshot
|
||||
a server the owner asks on an open hosting connection with the file's owner token
|
||||
(`Admin::RemovePeer {file, client}`; the operator may too, with the server key). Addresses are
|
||||
the only identity a session has: someone behind the same address (one NAT) is barred too.
|
||||
<!-- end feat: remove-from-session -->
|
||||
- **Text carets.** While someone types in a text layer of the shared document on your page, their
|
||||
caret and selected characters show on it in their colour (the text layout's own caret and
|
||||
selection geometry, so they sit at the right glyphs of your copy), with their name in a tag above
|
||||
|
||||
@@ -1090,13 +1090,11 @@ only: a host stops hosting it, a guest leaves it.
|
||||
pointer. Your selection, page and view go out by themselves when they change.
|
||||
- `collab.follow {client}` follows that person's viewport; `collab.follow {}` stops (so does any
|
||||
pan or zoom of your own, or Escape).
|
||||
<!-- start feat: remove-from-session -->
|
||||
- `collab.removePerson {client}` removes that person from the session: they are told the session
|
||||
is over for them (they keep their copy and don't reconnect), and their address can't join the
|
||||
file again while the session (or the server) runs. Only the person hosting, or the owner of a
|
||||
file on a server (with its owner token), can; a server older than this request says it can't.
|
||||
Returns `{removed, name}`.
|
||||
<!-- end feat: remove-from-session -->
|
||||
- `collab.cursorChat {text}` shows `text` next to your cursor for everyone in the session (put the
|
||||
cursor somewhere first with `collab.cursor`): at most 100 characters, control characters become
|
||||
spaces, `""` closes it. Others see it fade 5 seconds after its last change, so send it again to
|
||||
|
||||
Reference in New Issue
Block a user