T22: Collaboration
- presence: avatars ringed in each peer's color with where they are, press to follow; a breathing live dot; follow mode frames the view and stops on Escape - remote cursors and selections painted over a field from its own layout - comment threads with replies, reactions and resolve; a marker that opens a thread in a bubble; a sidebar of open and resolved threads - annotations washed in their authors' colors, notes on hover - suggestion mode: edits become suggestions word by word; track changes shows them struck and underlined, to accept or reject - sharing: invite by email with checked chips, roles, the access list and who the link lets in, in one dialog - primitives::hold_focus gives a mode its own focus, shared with ZenMode - AvatarGroup's tuck and count shared; Avatar::ring_color; TagInput::check; TextInput::bounds_for_range; Popover::size - icons: smile-plus, user-plus, message-square-diff - gallery: the Collaboration page with scripted states
This commit is contained in:
34 files changed
+3297
-86
No files matched your search
@@ -0,0 +1,168 @@
|
||||
use ely_gpui_component::{
|
||||
buttons::{Button, ButtonVariant},
|
||||
collab::{AccessList, InviteInput, LinkAccess, Member, Role, ShareDialog},
|
||||
data_display::{Activity, ActivityFeed},
|
||||
forms::is_email,
|
||||
primitives::IconName,
|
||||
};
|
||||
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
||||
use jiff::{Timestamp, ToSpan};
|
||||
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{keep, section, set},
|
||||
};
|
||||
|
||||
fn member(name: &str, email: &str, role: Role) -> Member {
|
||||
Member {
|
||||
name: name.to_string().into(),
|
||||
email: email.to_string().into(),
|
||||
picture: None,
|
||||
role,
|
||||
owner: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn members() -> Vec<Member> {
|
||||
vec![
|
||||
Member {
|
||||
owner: true,
|
||||
..member("You", "you@example.com", Role::Edit)
|
||||
},
|
||||
member("Ada Park", "ada@example.com", Role::Edit),
|
||||
member("Grace Lin", "grace@example.com", Role::Comment),
|
||||
member("Alan Reyes", "alan@example.com", Role::View),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn sharing(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let open = keep("collab-share-open", || false, window, cx);
|
||||
let emails = keep(
|
||||
"collab-emails",
|
||||
|| {
|
||||
vec![
|
||||
SharedString::from("mae@example.com"),
|
||||
SharedString::from("tim@example"),
|
||||
]
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let role = keep("collab-invite-role", || Role::Edit, window, cx);
|
||||
let people = keep("collab-members", members, window, cx);
|
||||
let link = keep("collab-link", || None::<Role>, window, cx);
|
||||
let dialog = open.read(cx).then(|| {
|
||||
let (now_emails, now_role, now_people, now_link) = (
|
||||
emails.read(cx).clone(),
|
||||
*role.read(cx),
|
||||
people.read(cx).clone(),
|
||||
*link.read(cx),
|
||||
);
|
||||
let (close, typed, invited, cleared, picked) = (
|
||||
open.clone(),
|
||||
emails.clone(),
|
||||
people.clone(),
|
||||
emails.clone(),
|
||||
role.clone(),
|
||||
);
|
||||
let (roled, removed) = (people.clone(), people.clone());
|
||||
ShareDialog::new("collab-share", "Lift", move |_, cx| set(&close, false, cx))
|
||||
.invite(
|
||||
InviteInput::new(
|
||||
"collab-invite",
|
||||
now_emails,
|
||||
move |next, _, cx| set(&typed, next.to_vec(), cx),
|
||||
move |_, cx| {
|
||||
let (added, role) = (cleared.read(cx).clone(), *picked.read(cx));
|
||||
invited.update(cx, |people, cx| {
|
||||
people.extend(added.iter().filter(|email| is_email(email)).map(
|
||||
|email| {
|
||||
let (name, _) =
|
||||
email.split_once('@').expect("an address holds @");
|
||||
member(name, email, role)
|
||||
},
|
||||
));
|
||||
cx.notify();
|
||||
});
|
||||
set(&cleared, Vec::new(), cx)
|
||||
},
|
||||
)
|
||||
.role(now_role, move |next, _, cx| set(&role, next, cx)),
|
||||
)
|
||||
.access(
|
||||
AccessList::new("collab-access", now_people)
|
||||
.on_role(move |ix, next, _, cx| {
|
||||
roled.update(cx, |people, cx| {
|
||||
people[ix].role = next;
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.on_remove(move |ix, _, cx| {
|
||||
removed.update(cx, |people, cx| {
|
||||
people.remove(ix);
|
||||
cx.notify();
|
||||
})
|
||||
}),
|
||||
)
|
||||
.link(LinkAccess::new(
|
||||
"collab-link",
|
||||
"https://example.com/d/lift",
|
||||
now_link,
|
||||
move |next, _, cx| set(&link, next, cx),
|
||||
))
|
||||
});
|
||||
section(
|
||||
"ShareDialog / InviteInput / PermissionSelect / AccessList",
|
||||
"Sharing in one place: add people by email, each chip marked until it is an address; choose what they may do; change or take away anyone's access; and choose who the link lets in.",
|
||||
cx,
|
||||
)
|
||||
.child(probe(
|
||||
"collab-share",
|
||||
div().flex().child(
|
||||
Button::new("collab-share-open", "Share")
|
||||
.variant(ButtonVariant::Primary)
|
||||
.icon(IconName::UserPlus)
|
||||
.on_click(move |_, _, cx| set(&open, true, cx)),
|
||||
),
|
||||
))
|
||||
.children(dialog)
|
||||
}
|
||||
|
||||
pub fn activity(cx: &mut App) -> impl IntoElement + use<> {
|
||||
let now = Timestamp::now();
|
||||
let entry = |key: &str, actor: &str, verb: &str, object: &str, minutes: i64| {
|
||||
Activity::new(
|
||||
key.to_string(),
|
||||
actor.to_string(),
|
||||
verb.to_string(),
|
||||
now - minutes.minutes(),
|
||||
)
|
||||
.object(object.to_string())
|
||||
};
|
||||
section(
|
||||
"ActivityLog → data_display::ActivityFeed",
|
||||
"What people did in the document, newest first.",
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
div().w(px(560.)).child(
|
||||
ActivityFeed::new("collab-activity")
|
||||
.entry(entry(
|
||||
"reply",
|
||||
"Alan Reyes",
|
||||
"replied on",
|
||||
"blends in gamma space",
|
||||
4,
|
||||
))
|
||||
.entry(entry(
|
||||
"suggest",
|
||||
"Grace Lin",
|
||||
"suggested a change to",
|
||||
"Lift",
|
||||
35,
|
||||
))
|
||||
.entry(entry("resolve", "Ada Park", "resolved", "On Lift", 180))
|
||||
.entry(entry("share", "You", "shared", "Lift", 1440)),
|
||||
),
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user