chat + contacts: finished integration for managed groups

Specifically, this commit removes the add action from the contact-view
and replaces it with a listener within contact-hook for additions
to groups. This means that when a ship is added to a group that the
contact-hook is watching, the ship is automatically sent an invite to
join that "managed group" from the contacts application. This also
includes the UI integration work on the management screen and settings
screen for working with the new group / permission structure.
This commit is contained in:
Logan Allen 2020-02-06 15:55:10 -08:00
parent 64ff95d27c
commit ea93dd3af8
17 changed files with 148 additions and 176 deletions

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
@ -236,8 +242,12 @@
?^ (group-scry path) ~
:: do not create a managed group if this is a sig path or a blacklist
::
?: |(=(i.path '~') ?!(=(security %village)))
?: =(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

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);
});
}