Merge pull request #2243 from urbit/la-chat-sig-path

chat + contacts: finished integration for managed groups
This commit is contained in:
matildepark 2020-02-06 19:29:18 -05:00 committed by GitHub
commit 140e1b4d9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 165 additions and 192 deletions

View File

@ -363,7 +363,8 @@
:_ state
[%pass /permissions %agent [our.bol %permission-store] %watch /updates]~
::
?: ?=([%mailbox @ *] wir)
?+ wir !!
[%mailbox @ *]
~& mailbox-kick+wir
?. (~(has by synced) t.wir)
:: no-op
@ -377,13 +378,17 @@
:_ state
[%pass chat-history %agent [ship %chat-hook] %watch chat-history]~
::
?. ?=([%backlog @ *] wir) !!
=/ pax `path`(oust [(dec (lent t.wir)) 1] `(list @ta)`t.wir)
?. (~(has by synced) pax) [~ state]
=/ mailbox=(unit mailbox) (chat-scry pax)
=. pax ?~(mailbox wir [%mailbox pax])
:_ state
[%pass pax %agent [(slav %p i.t.wir) %chat-hook] %watch pax]~
[%backlog @ @ *]
=/ pax `path`(oust [(dec (lent t.wir)) 1] `(list @ta)`t.wir)
?. (~(has by synced) pax) [~ state]
=/ =ship
?: =('~' i.t.wir)
(slav %p i.t.t.wir)
(slav %p i.t.wir)
=. pax ?~((chat-scry pax) wir [%mailbox pax])
:_ state
[%pass pax %agent [ship %chat-hook] %watch pax]~
==
::
++ watch-ack
|= [wir=wire saw=(unit tang)]

View File

@ -197,6 +197,7 @@
?> (team:title our.bol src.bol)
?- -.act
%create
?> ?=(^ path.act)
?^ (chat-scry path.act)
~& %chat-already-exists
~
@ -211,9 +212,14 @@
==
::
%delete
:~ (chat-hook-poke [%remove path.act])
(permission-hook-poke [%remove path.act])
(chat-poke [%delete path.act])
?> ?=(^ path.act)
%- zing
:~ :~ (chat-hook-poke [%remove path.act])
(permission-hook-poke [%remove path.act])
(chat-poke [%delete path.act])
==
?. =(i.path.act '~') ~
~[(group-poke [%unbundle path.act])]
==
::
%join
@ -232,16 +238,16 @@
++ create-managed-group
|= [=path security=rw-security ships=(set ship)]
^- (list card)
~& [path security ships]
?. =(security %village)
:: if blacklist, do nothing but create the group if it isn't there
::
?~((group-scry path) ~[(group-poke [%bundle path])] ~)
:: if whitelist, check if group exists already. if yes, do nothing
::
?> ?=(^ path)
?^ (group-scry path) ~
:: if group does not exist, send contact-view %create
:: do not create a managed group if this is a sig path or a blacklist
::
?: =(security %channel)
~[(group-poke [%bundle path])]
?: =(i.path '~')
:~ (group-poke [%bundle path])
(group-poke [%add ships path])
==
~[(contact-view-poke [%create path ships])]
::
++ create-security

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -295,9 +295,19 @@
^- (quip card _state)
|^
?+ -.fact [~ state]
%add (add +.fact)
%remove (remove +.fact)
%unbundle (unbundle +.fact)
==
++ add
|= [ships=(set ship) =path]
^- (quip card _state)
?. (~(has by synced) path)
[~ state]
:_ state
%+ turn ~(tap in (~(del in ships) our.bol))
|= =ship
(send-invite-poke path ship)
::
++ unbundle
|= =path
@ -328,6 +338,16 @@
(contact-poke [%delete path])
(contact-poke [%remove path ship])
==
::
++ send-invite-poke
|= [=path =ship]
^- card
=/ =invite
:* our.bol %contact-hook
path ship ''
==
=/ act=invite-action [%invite /contacts (shaf %msg-uid eny.bol) invite]
[%pass / %agent [our.bol %invite-hook] %poke %invite-action !>(act)]
--
::
++ fact-invite-update

View File

@ -126,27 +126,18 @@
?- -.act
%create
?> ?=([@ *] path.act)
%+ welp
:~ (group-poke [%bundle path.act])
(contact-poke [%create path.act])
(contact-hook-poke [%add-owned path.act])
(group-hook-poke [%add our.bol path.act])
(group-poke [%add (~(put in ships.act) our.bol) path.act])
==
%+ turn ~(tap in (~(del in ships.act) our.bol))
|= =ship
(send-invite-poke path.act ship)
:~ (group-poke [%bundle path.act])
(contact-poke [%create path.act])
(contact-hook-poke [%add-owned path.act])
(group-hook-poke [%add our.bol path.act])
(group-poke [%add (~(put in ships.act) our.bol) path.act])
==
::
%delete
:~ (group-poke [%unbundle path.act])
(contact-poke [%delete path.act])
(contact-hook-poke [%remove path.act])
==
::
%add
%+ welp [(group-poke [%add ships.act path.act])]~
%+ turn ~(tap in (~(del in ships.act) our.bol))
|=(=ship (send-invite-poke path.act ship))
::
%remove
:~ (group-poke [%remove [ship.act ~ ~] path.act])
@ -227,16 +218,6 @@
^- card
[%pass / %agent [our.bol %group-hook] %poke %group-hook-action !>(act)]
::
++ send-invite-poke
|= [=path =ship]
^- card
=/ =invite
:* our.bol %contact-hook
path ship ''
==
=/ act=invite-action [%invite /contacts (shaf %msg-uid eny.bol) invite]
[%pass / %agent [our.bol %invite-hook] %poke %invite-action !>(act)]
::
++ all-scry
^- rolodex
.^(rolodex %gx /=contact-store/(scot %da now.bol)/all/noun)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -101,7 +101,6 @@
%- of
:~ [%create create]
[%delete delete]
[%add add]
[%remove remove]
[%share share]
==
@ -114,12 +113,6 @@
::
++ delete (ot [%path pa]~)
::
++ add
%- ot
:~ [%path pa]
[%ships (as (su ;~(pfix sig fed:ag)))]
==
::
++ remove
%- ot
:~ [%path pa]

View File

@ -4,9 +4,6 @@
$% :: %create: create in both groups and contacts
::
[%create =path ships=(set ship)]
:: %add: add to groups and send invites
::
[%add =path ships=(set ship)]
:: %remove: remove from both groups and contacts
::
[%remove =path =ship]

View File

@ -17,7 +17,6 @@ export class ChatScreen extends Component {
super(props);
this.state = {
station: `/${props.match.params.ship}/${props.match.params.station}`,
numPages: 1,
scrollLocked: false
};
@ -80,7 +79,7 @@ export class ChatScreen extends Component {
updateReadNumber() {
const { props, state } = this;
if (props.read < props.length) {
props.api.chat.read(state.station);
props.api.chat.read(props.station);
}
}
@ -105,7 +104,7 @@ export class ChatScreen extends Component {
this.hasAskedForMessages = true;
props.subscription.fetchMessages(start, end - 1, state.station);
props.subscription.fetchMessages(start, end - 1, props.station);
}
}
}
@ -181,8 +180,8 @@ export class ChatScreen extends Component {
);
}
let pendingMessages = props.pendingMessages.has(state.station)
? props.pendingMessages.get(state.station)
let pendingMessages = props.pendingMessages.has(props.station)
? props.pendingMessages.get(props.station)
: [];
pendingMessages.map(function(value) {
@ -225,7 +224,7 @@ export class ChatScreen extends Component {
return (
<div
key={state.station}
key={props.station}
className="h-100 w-100 overflow-hidden flex flex-column">
<div
className="w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8"
@ -240,17 +239,17 @@ export class ChatScreen extends Component {
sidebarShown={this.props.sidebarShown}
popout={this.props.popout}
/>
<Link to={`/~chat/` + isinPopout + `room` + state.station}
<Link to={`/~chat/` + isinPopout + `room` + props.station}
className="pt2 white-d">
<h2
className="mono dib f8 fw4 v-top"
style={{ width: "max-content" }}>
{state.station.substr(1)}
{props.station.substr(1)}
</h2>
</Link>
<ChatTabBar
{...props}
station={state.station}
station={props.station}
numPeers={group.length}
isOwner={deSig(props.match.params.ship) === window.ship}
popout={this.props.popout}
@ -269,7 +268,7 @@ export class ChatScreen extends Component {
<ChatInput
api={props.api}
numMsgs={lastMsgNum}
station={state.station}
station={props.station}
owner={deSig(props.match.params.ship)}
ownerContact={ownerContact}
permissions={props.permissions}

View File

@ -54,7 +54,7 @@ export class InviteElement extends Component {
props.api.groups.add(aud, props.path);
if (props.permissions.kind === 'white') {
aud.forEach((ship) => {
props.api.invite.invite(props.station, ship);
props.api.invite.invite(props.path, ship);
});
}
});

View File

@ -166,7 +166,8 @@ export class Message extends Component {
let name = props.msg.author;
let color = "#000000";
if (contact) {
name = contact.nickname;
name = (contact.nickname.length > 0)
? contact.nickname : props.msg.author;
color = `#${uxToHex(contact.color)}`;
}

View File

@ -13,43 +13,23 @@ import { SidebarSwitcher } from '/components/lib/icons/icon-sidebar-switch.js';
export class MemberScreen extends Component {
constructor(props) {
super(props);
this.state = {
station: `/${props.match.params.ship}/${props.match.params.station}`,
};
}
render() {
const { props, state } = this;
let writeGroup = Array.from(props.write.who.values());
let readGroup = Array.from(props.read.who.values());
let perm = Array.from(props.permission.who.values());
let writeText = '';
let readText = '';
let modWriteText = '';
let modReadText = '';
let memberText = '';
let modifyText = '';
if (props.write.kind === 'black') {
writeText = 'Everyone banned from writing to this chat.';
modWriteText = 'Ban someone from writing to this chat.';
} else if (props.write.kind === 'white') {
writeText = 'Everyone with permission to message this chat.';
modWriteText = 'Invite someone to write to this chat.';
if (props.permission.kind === 'black') {
memberText = 'Everyone banned from accessing this chat.';
modifyText = 'Ban someone from accessing this chat.';
} else if (props.permission.kind === 'white') {
memberText = 'Everyone with permission to access this chat.';
modifyText = 'Invite someone to this chat.';
}
if (props.read.kind === 'black') {
readText = 'Everyone banned from reading this chat.';
modReadText = 'Ban someone from reading this chat.';
} else if (props.read.kind === 'white') {
readText = 'Everyone with permission to read this chat.';
modReadText = 'Invite someone to read this chat.';
}
let writeListMembers = writeGroup.map((mem) => {
let members = perm.map((mem) => {
let contact = (mem in props.contacts)
? props.contacts[mem] : false;
@ -59,28 +39,12 @@ export class MemberScreen extends Component {
owner={deSig(props.match.params.ship)}
contact={contact}
ship={mem}
path={`/chat${state.station}/write`}
kind={props.write.kind}
path={props.station}
kind={props.permission.kind}
api={props.api} />
);
});
let readListMembers = readGroup.map((mem) => {
let contact = (mem in props.contacts)
? props.contacts[mem] : false;
return (
<MemberElement
key={mem}
owner={deSig(props.match.params.ship)}
contact={contact}
ship={mem}
path={`/chat${state.station}/read`}
kind={props.read.kind}
api={props.api} />
);
});
let isinPopout = this.props.popout ? "popout/" : "";
return (
@ -98,18 +62,18 @@ export class MemberScreen extends Component {
sidebarShown={this.props.sidebarShown}
popout={this.props.popout}
/>
<Link to={`/~chat/` + isinPopout + `room` + state.station}
<Link to={`/~chat/` + isinPopout + `room` + props.station}
className="pt2 white-d">
<h2
className="mono dib f8 fw4 v-top"
style={{ width: "max-content" }}>
{state.station.substr(1)}
{props.station.substr(1)}
</h2>
</Link>
<ChatTabBar
{...props}
station={state.station}
numPeers={writeGroup.length}
station={props.station}
numPeers={perm.length}
isOwner={deSig(props.match.params.ship) === window.ship}
popout={this.props.popout}
/>
@ -117,34 +81,16 @@ export class MemberScreen extends Component {
<div className="w-100 pl3 mt0 mt4-m mt4-l mt4-xl cf pr6">
<div className="w-100 w-50-l w-50-xl fl pa2 pr3 pt3 pt0-l pt0-xl">
<p className="f8 pb2">Members</p>
<p className="f9 gray2 mb3">{writeText}</p>
{writeListMembers}
<p className="f9 gray2 mb3">{memberText}</p>
{members}
</div>
<div className="w-100 w-50-l w-50-xl fl pa2 pr3 pt3 pt0-l pt0-xl">
<p className="f8 pb2">Modify Permissions</p>
<p className="f9 gray2 mb3">{modWriteText}</p>
<p className="f9 gray2 mb3">{modifyText}</p>
{window.ship === deSig(props.match.params.ship) ? (
<InviteElement
path={`/chat${state.station}/write`}
station={`/${props.match.params.station}`}
permissions={props.write}
api={props.api}
/>
) : null}
</div>
</div>
<div className="w-100 pl3 mt4 cf pr6">
<div className="w-100 w-50-l w-50-xl fl pa2 pr3 pt3 pt0-l pt0-xl">
<p className="f9 gray2 db mb3">{readText}</p>
{readListMembers}
</div>
<div className="w-100 w-50-l w-50-xl fl pa2 pr3 pt3 pt0-l pt0-xl">
<p className="f9 gray2 db mb3">{modReadText}</p>
{window.ship === deSig(props.match.params.ship) ? (
<InviteElement
path={`/chat${state.station}/read`}
station={`/${props.match.params.station}`}
permissions={props.read}
path={props.station}
permissions={props.permission}
api={props.api}
/>
) : null}

View File

@ -16,11 +16,11 @@ export class NewScreen extends Component {
groups: [],
ships: []
},
security: 'channel',
security: 'village',
idError: false,
inviteError: false,
allowHistory: true,
createGroup: false
createGroup: true
};
this.idChange = this.idChange.bind(this);
@ -58,8 +58,18 @@ export class NewScreen extends Component {
this.setState({security: "channel"});
}
}
createGroupChange(event) {
this.setState({createGroup: !!event.target.checked});
if (event.target.checked) {
this.setState({
createGroup: !!event.target.checked,
security: 'village'
});
} else {
this.setState({
createGroup: !!event.target.checked,
});
}
}
allowHistoryChange(event) {
@ -72,7 +82,7 @@ export class NewScreen extends Component {
let validChar = /^[a-z0-9~_.-]*$/;
let invalid = (
(!state.idName) || (!validChara.test(state.idName))
(!state.idName) || (!validChar.test(state.idName))
);
if (invalid) {
@ -135,27 +145,28 @@ export class NewScreen extends Component {
// if we want a "proper group" that can be managed from the contacts UI,
// we make a path of the form /~zod/cool-group
// if not, we make a path of the form /~/~zod/free-chat
let chatPath = `/~${window.ship}${station}`;
if (!state.createGroup || state.invites.groups.length > 0) {
chatPath = '/~' + chatPath;
}
props.api.chatView.create(
`/~${window.ship}${station}`, state.security, aud, state.allowHistory
chatPath, state.security, aud, state.allowHistory
);
});
}
render() {
let inviteSwitchClasses =
"relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0";
if (this.state.security === "village") {
inviteSwitchClasses = "relative checked bg-green2 br3 h1 toggle v-mid z-0";
}
let inviteSwitchClasses = (this.state.security === "village")
? "relative checked bg-green2 br3 h1 toggle v-mid z-0"
: "relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0";
let createGroupClasses = this.state.createGroup
? "relative checked bg-green2 br3 h1 toggle v-mid z-0"
: "relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0";
let createClasses = "pointer db f9 green2 bg-gray0-d ba pv3 ph4 b--green2";
if (!this.state.idName) {
createClasses = 'pointer db f9 gray2 ba bg-gray0-d pa2 pv3 ph4 b--gray3';
}
let createClasses = !!this.state.idName
? "pointer db f9 green2 bg-gray0-d ba pv3 ph4 b--green2"
: "pointer db f9 gray2 ba bg-gray0-d pa2 pv3 ph4 b--gray3";
let idErrElem = (<span />);
if (this.state.idError) {
@ -167,7 +178,7 @@ export class NewScreen extends Component {
}
let createGroupToggle = <div/>
if ((this.state.invites.ships.length > 0) && (this.state.invites.groups.length === 0)) {
if (this.state.invites.groups.length === 0) {
createGroupToggle = (
<div className="mv7">
<input

View File

@ -112,7 +112,7 @@ export class Root extends Component {
/>
<Route
exact
path="/~chat/join/:ship?/:station?"
path="/~chat/join/(~)?/:ship?/:station?"
render={props => {
let station =
props.match.params.ship
@ -131,9 +131,13 @@ export class Root extends Component {
/>
<Route
exact
path="/~chat/(popout)?/room/:ship/:station+"
path="/~chat/(popout)?/room/(~)?/:ship/:station+"
render={props => {
let station = `/${props.match.params.ship}/${props.match.params.station}`;
let sig = props.match.url.includes("/~/");
if (sig) {
station = '/~' + station;
}
let mailbox = state.inbox[station] || {
config: {
read: 0,
@ -142,13 +146,10 @@ export class Root extends Component {
envelopes: []
};
/*let defaultContacts = ('/~/default' in contacts)
? contacts['/~/default'] : {};*/
let roomContacts = (station in contacts)
? contacts[station] : {};
let write = state.groups[`/chat${station}/write`] || new Set([]);
let group = state.groups[station] || new Set([]);
let popout = props.match.url.includes("/popout/");
return (
@ -159,13 +160,14 @@ export class Root extends Component {
sidebar={renderChannelSidebar(props)}
>
<ChatScreen
station={station}
api={api}
subscription={subscription}
read={mailbox.config.read}
length={mailbox.config.length}
envelopes={mailbox.envelopes}
inbox={state.inbox}
group={write}
group={group}
contacts={roomContacts}
permissions={state.permissions}
pendingMessages={state.pendingMessages}
@ -179,14 +181,15 @@ export class Root extends Component {
/>
<Route
exact
path="/~chat/(popout)?/members/:ship/:station+"
path="/~chat/(popout)?/members/(~)?/:ship/:station+"
render={props => {
let station = `/${props.match.params.ship}/${props.match.params.station}`;
let read = state.permissions[`/chat${station}/read`] || {
kind: "",
who: new Set([])
};
let write = state.permissions[`/chat${station}/write`] || {
let sig = props.match.url.includes("/~/");
if (sig) {
station = '/~' + station;
}
let permission = state.permissions[station] || {
kind: "",
who: new Set([])
};
@ -205,8 +208,8 @@ export class Root extends Component {
<MemberScreen
{...props}
api={api}
read={read}
write={write}
station={station}
permission={permission}
contacts={roomContacts}
permissions={state.permissions}
popout={popout}
@ -218,10 +221,15 @@ export class Root extends Component {
/>
<Route
exact
path="/~chat/(popout)?/settings/:ship/:station+"
path="/~chat/(popout)?/settings/(~)?/:ship/:station+"
render={props => {
let station = `/${props.match.params.ship}/${props.match.params.station}`;
let write = state.groups[`/chat${station}/write`] || new Set([]);
let station =
`/${props.match.params.ship}/${props.match.params.station}`;
let sig = props.match.url.includes("/~/");
if (sig) {
station = '/~' + station;
}
let group = state.groups[station] || new Set([]);
let popout = props.match.url.includes("/popout/");
@ -235,9 +243,11 @@ export class Root extends Component {
>
<SettingsScreen
{...props}
station={station}
setSpinner={this.setSpinner}
api={api}
group={write}
station={station}
group={group}
inbox={state.inbox}
popout={popout}
sidebarShown={state.sidebarShown}

View File

@ -14,7 +14,6 @@ export class SettingsScreen extends Component {
super(props);
this.state = {
station: `/${props.match.params.ship}/${props.match.params.station}`,
isLoading: false
};
@ -23,7 +22,7 @@ export class SettingsScreen extends Component {
componentDidUpdate(prevProps, prevState) {
const { props, state } = this;
if (!!state.isLoading && !(state.station in props.inbox)) {
if (!!state.isLoading && !(props.station in props.inbox)) {
this.setState({
isLoading: false
}, () => {
@ -36,7 +35,7 @@ export class SettingsScreen extends Component {
deleteChat() {
const { props, state } = this;
props.api.chatView.delete(state.station);
props.api.chatView.delete(props.station);
props.setSpinner(true);
this.setState({
@ -96,17 +95,17 @@ export class SettingsScreen extends Component {
sidebarShown={this.props.sidebarShown}
popout={this.props.popout}
/>
<Link to={`/~chat/` + isinPopout + `room` + state.station}
<Link to={`/~chat/` + isinPopout + `room` + props.station}
className="pt2 white-d">
<h2
className="mono dib f8 fw4 v-top"
style={{ width: "max-content" }}>
{state.station.substr(1)}
{props.station.substr(1)}
</h2>
</Link>
<ChatTabBar
{...props}
station={state.station}
station={props.station}
numPeers={writeGroup.length}
/>
</div>
@ -131,17 +130,17 @@ export class SettingsScreen extends Component {
sidebarShown={this.props.sidebarShown}
popout={this.props.popout}
/>
<Link to={`/~chat/` + isinPopout + `room` + state.station}
<Link to={`/~chat/` + isinPopout + `room` + props.station}
className="pt2">
<h2
className="mono dib f8 fw4 v-top"
style={{ width: "max-content" }}>
{state.station.substr(1)}
{props.station.substr(1)}
</h2>
</Link>
<ChatTabBar
{...props}
station={state.station}
station={props.station}
numPeers={writeGroup.length}
isOwner={deSig(props.match.params.ship) === window.ship}
popout={this.props.popout}

View File

@ -18,9 +18,12 @@ class UrbitApi {
this.contactView = {
create: this.contactCreate.bind(this),
delete: this.contactDelete.bind(this),
add: this.contactAdd.bind(this),
share: this.contactShare.bind(this)
};
this.group = {
add: this.groupAdd.bind(this)
};
this.invite = {
accept: this.inviteAccept.bind(this),
@ -69,8 +72,10 @@ class UrbitApi {
this.contactViewAction({ create: { path, ships }});
}
contactAdd(path, ships = []) {
this.contactViewAction({ add: { path, ships }});
groupAdd(path, ships = []) {
this.action("group-store", "group-action", {
add: { members: ships, path }
});
}
contactShare(recipient, path, ship, contact) {

View File

@ -55,7 +55,7 @@ export class AddScreen extends Component {
invites: ''
}, () => {
props.setSpinner(true);
props.api.contactView.add(props.path, aud)
props.api.group.add(props.path, aud)
props.history.push('/~contacts' + props.path);
});
}