chat-js: update frontend for new group-store

This commit is contained in:
Liam Fitzgerald 2020-07-02 11:12:54 +10:00
parent 2bf1969312
commit 7d3a7a166b
9 changed files with 129 additions and 149 deletions

View File

@ -15,6 +15,7 @@ import { PatpNoSig } from '../../types/noun';
import GlobalApi from '../../api/global';
import { StoreState } from '../../store/type';
import GlobalSubscription from '../../subscription/global';
import {groupBunts} from '../../types/group-update';
type ChatAppProps = StoreState & {
ship: PatpNoSig;
@ -97,11 +98,11 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
sidebarShown,
inbox,
contacts,
permissions,
chatSynced,
api,
chatInitialized,
pendingMessages
pendingMessages,
groups
} = props;
const renderChannelSidebar = (props, station?) => (
@ -161,7 +162,7 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
<NewDmScreen
api={api}
inbox={inbox}
permissions={permissions || {}}
groups={groups || {}}
contacts={contacts || {}}
associations={associations.contacts}
chatSynced={chatSynced || {}}
@ -187,7 +188,7 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
<NewScreen
api={api}
inbox={inbox || {}}
permissions={permissions || {}}
groups={groups}
contacts={contacts || {}}
associations={associations.contacts}
chatSynced={chatSynced || {}}
@ -199,13 +200,10 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
/>
<Route
exact
path="/~chat/join/(~)?/:ship?/:station?"
path="/~chat/join/:ship?/:station?"
render={(props) => {
let station = `/${props.match.params.ship}/${props.match.params.station}`;
const sig = props.match.url.includes('/~/');
if (sig) {
station = '/~' + station;
}
return (
<Skeleton
@ -257,13 +255,8 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
const association =
station in associations['chat'] ? associations.chat[station] : {};
const permission =
station in permissions
? permissions[station]
: {
who: new Set([]),
kind: 'white'
};
const group = groups[association['group-path']] || groupBunts.group();
const popout = props.match.url.includes('/popout/');
return (
@ -285,7 +278,7 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
envelopes={mailbox.envelopes}
inbox={inbox}
contacts={roomContacts}
permission={permission}
group={group}
pendingMessages={pendingMessages}
s3={s3}
popout={popout}
@ -302,20 +295,14 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
path="/~chat/(popout)?/members/(~)?/:ship/:station+"
render={(props) => {
let station = `/${props.match.params.ship}/${props.match.params.station}`;
const sig = props.match.url.includes('/~/');
if (sig) {
station = '/~' + station;
}
const permission = permissions[station] || {
kind: '',
who: new Set([])
};
const popout = props.match.url.includes('/popout/');
const association =
station in associations['chat'] ? associations.chat[station] : {};
const groupPath = association['group-path'];
const group = groups[groupPath] || {};
return (
<Skeleton
associations={associations}
@ -328,11 +315,12 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
<MemberScreen
{...props}
api={api}
group={group}
groups={groups}
associations={associations}
station={station}
association={association}
permission={permission}
contacts={contacts}
permissions={permissions}
popout={popout}
sidebarShown={sidebarShown}
/>
@ -352,13 +340,9 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
const popout = props.match.url.includes('/popout/');
const permission = permissions[station] || {
kind: '',
who: new Set([])
};
const association =
station in associations['chat'] ? associations.chat[station] : {};
const group = groups[association['group-path']] || groupBunts.group();
return (
<Skeleton
@ -373,8 +357,8 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
{...props}
station={station}
association={association}
permission={permission}
permissions={permissions || {}}
groups={groups || {}}
group={group}
contacts={contacts || {}}
associations={associations.contacts}
api={api}

View File

@ -19,6 +19,7 @@ import { Contacts } from "../../../types/contact-update";
import { Path, Patp } from "../../../types/noun";
import GlobalApi from "../../../api/global";
import { Association } from "../../../types/metadata-update";
import {Group} from "../../../types/group-update";
function getNumPending(props: any) {
const result = props.pendingMessages.has(props.station)
@ -79,7 +80,7 @@ type ChatScreenProps = RouteComponentProps<{
length: number;
inbox: Inbox;
contacts: Contacts;
permission: any;
group: Group;
pendingMessages: Map<Path, Envelope[]>;
s3: any;
popout: boolean;
@ -503,7 +504,7 @@ export class ChatScreen extends Component<ChatScreenProps, ChatScreenState> {
const lastMsgNum = messages.length > 0 ? messages.length : 0;
const group = Array.from(props.permission.who.values());
const group = Array.from(props.group.members);
const isinPopout = props.popout ? "popout/" : "";

View File

@ -27,12 +27,10 @@ export class JoinScreen extends Component {
props.autoJoin !== '/~/undefined/undefined') &&
(props.api && (prevProps?.api !== props.api))) {
let station = props.autoJoin.split('/');
const sig = props.autoJoin.includes('/~/');
const ship = sig ? station[2] : station[1];
const ship = station[1];
if (
station.length < 2 ||
(Boolean(sig) && station.length < 3) ||
!urbitOb.isValidPatp(ship)
) {
this.setState({
@ -59,12 +57,10 @@ export class JoinScreen extends Component {
const { props, state } = this;
let station = state.station.split('/');
const sig = state.station.includes('/~/');
const ship = sig ? station[2] : station[1];
const ship = station[1];
if (
station.length < 2 ||
(Boolean(sig) && station.length < 3) ||
!urbitOb.isValidPatp(ship)
) {
this.setState({
@ -84,7 +80,7 @@ export class JoinScreen extends Component {
stationChange(event) {
this.setState({
station: `/${event.target.value}`
station: `/${event.target.value.trim()}`
});
}

View File

@ -33,7 +33,7 @@ export class InviteElement extends Component {
members: [],
awaiting: true
}, () => {
props.api.groups.add(aud, props.path).then(() => {
props.api.chatView.invite(props.path, aud).then(() => {
this.setState({ awaiting: false });
});
});

View File

@ -7,44 +7,23 @@ import { ChatTabBar } from './lib/chat-tabbar';
import { MemberElement } from './lib/member-element';
import { InviteElement } from './lib/invite-element';
import { SidebarSwitcher } from '../../../components/SidebarSwitch';
import { GroupView } from '../../../components/Group';
import { PatpNoSig } from '../../../types/noun';
export class MemberScreen extends Component {
constructor(props) {
super(props);
this.inviteShips = this.inviteShips.bind(this);
}
inviteShips(ships) {
const { props } = this;
return props.api.chat.invite(props.station, ships.map(s => `~${s}`));
}
render() {
const { props } = this;
const perm = Array.from(props.permission.who.values());
let memberText = '';
let modifyText = '';
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.';
}
const contacts = (props.station in props.contacts)
? props.contacts[props.station] : {};
const members = perm.map((mem) => {
const contact = (mem in contacts)
? contacts[mem] : false;
return (
<MemberElement
key={mem}
owner={deSig(props.match.params.ship)}
contact={contact}
ship={mem}
path={props.station}
kind={props.permission.kind}
api={props.api}
/>
);
});
const isinPopout = this.props.popout ? 'popout/' : '';
let title = props.station.substr(1);
@ -57,12 +36,12 @@ export class MemberScreen extends Component {
}
return (
<div className="h-100 w-100 overflow-x-hidden flex flex-column white-d">
<div className='h-100 w-100 overflow-x-hidden flex flex-column white-d'>
<div
className="w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8"
className='w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8'
style={{ height: '1rem' }}
>
<Link to="/~chat/">{'⟵ All Chats'}</Link>
<Link to='/~chat/'>{'⟵ All Chats'}</Link>
</div>
<div
className={`pl4 pt2 bb b--gray4 b--gray1-d bg-gray0-d flex relative
@ -74,12 +53,15 @@ export class MemberScreen extends Component {
popout={this.props.popout}
api={this.props.api}
/>
<Link to={'/~chat/' + isinPopout + 'room' + props.station}
className="pt2 white-d"
<Link
to={'/~chat/' + isinPopout + 'room' + props.station}
className='pt2 white-d'
>
<h2
className={'dib f9 fw4 lh-solid v-top ' +
((title === props.station.substr(1)) ? 'mono' : '')}
className={
'dib f9 fw4 lh-solid v-top ' +
(title === props.station.substr(1) ? 'mono' : '')
}
style={{ width: 'max-content' }}
>
{title}
@ -88,30 +70,23 @@ export class MemberScreen extends Component {
<ChatTabBar
{...props}
station={props.station}
numPeers={perm.length}
numPeers={5}
isOwner={deSig(props.match.params.ship) === window.ship}
popout={this.props.popout}
api={props.api}
/>
</div>
<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">Modify Permissions</p>
<p className="f9 gray2 mb3">{modifyText}</p>
{window.ship === deSig(props.match.params.ship) ? (
<InviteElement
path={props.station}
permissions={props.permission}
contacts={props.contacts}
api={props.api}
/>
) : null}
</div>
<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">{memberText}</p>
{members}
</div>
<div className='w-100 pl3 mt0 mt4-m mt4-l mt4-xl cf pr6'>
{ props.association['group-path'] && (
<GroupView
permissions
group={props.group}
resourcePath={props.association['group-path'] || ''}
associations={props.associations}
groups={props.groups}
inviteShips={this.inviteShips}
contacts={props.contacts}
/> )}
</div>
</div>
);

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { Spinner } from '../../../components/Spinner';
import { Link } from 'react-router-dom';
import urbitOb from 'urbit-ob';
import React, { Component } from "react";
import { Spinner } from "../../../components/Spinner";
import { Link } from "react-router-dom";
import urbitOb from "urbit-ob";
export class NewDmScreen extends Component {
constructor(props) {
@ -43,9 +43,9 @@ export class NewDmScreen extends Component {
onClickCreate() {
const { props, state } = this;
const station = `/~/~${window.ship}/dm--${state.ship}`;
const station = `/ship/~${window.ship}/dm--${state.ship}`;
const theirStation = `/~/~${state.ship}/dm--${window.ship}`;
const theirStation = `/ship/~${state.ship}/dm--${window.ship}`;
if (station in props.inbox) {
props.history.push(`/~chat/room${station}`);
@ -57,6 +57,8 @@ export class NewDmScreen extends Component {
return;
}
const aud = state.ship !== window.ship ? [`~${state.ship}`] : [];
this.setState(
{
station
@ -65,12 +67,13 @@ export class NewDmScreen extends Component {
const groupPath = station;
props.api.chat.create(
`~${window.ship} <-> ~${state.ship}`,
'',
"",
station,
groupPath,
'village',
state.ship !== window.ship ? [`~${state.ship}`] : [],
true
{ invite: { pending: aud } },
aud,
true,
false
);
}
);
@ -80,12 +83,12 @@ export class NewDmScreen extends Component {
return (
<div
className={
'h-100 w-100 mw6 pa3 pt4 overflow-x-hidden ' +
'bg-gray0-d white-d flex flex-column'
"h-100 w-100 mw6 pa3 pt4 overflow-x-hidden " +
"bg-gray0-d white-d flex flex-column"
}
>
<div className="w-100 dn-m dn-l dn-xl inter pt1 pb6 f8">
<Link to="/~chat/">{'⟵ All Chats'}</Link>
<Link to="/~chat/">{"⟵ All Chats"}</Link>
</div>
<h2 className="mb3 f8">New DM</h2>
<div className="w-100">

View File

@ -14,7 +14,7 @@ export class NewScreen extends Component {
idName: '',
groups: [],
ships: [],
security: 'channel',
privacy: 'open',
idError: false,
inviteError: false,
allowHistory: true,
@ -27,6 +27,7 @@ export class NewScreen extends Component {
this.allowHistoryChange = this.allowHistoryChange.bind(this);
this.setInvite = this.setInvite.bind(this);
this.createGroupChange = this.createGroupChange.bind(this);
this.privacyChange = this.privacyChange.bind(this);
}
componentDidUpdate(prevProps, prevState) {
@ -65,13 +66,23 @@ export class NewScreen extends Component {
createGroupChange(event) {
if (event.target.checked) {
this.setState({
createGroup: Boolean(event.target.checked),
security: 'village'
createGroup: Boolean(event.target.checked)
});
} else {
this.setState({
createGroup: Boolean(event.target.checked),
security: 'channel'
createGroup: Boolean(event.target.checked)
});
}
}
privacyChange(event) {
if (event.target.checked) {
this.setState({
privacy: 'open'
});
} else {
this.setState({
privacy: 'invite'
});
}
}
@ -111,7 +122,7 @@ export class NewScreen extends Component {
}
});
if(state.ships.length === 1 && state.security === 'village' && !state.createGroup) {
if(state.ships.length === 1 && state.privacy === 'invite' && !state.createGroup) {
props.history.push(`/~chat/new/dm/${aud[0]}`);
}
@ -128,6 +139,8 @@ export class NewScreen extends Component {
this.textarea.value = '';
}
const policy = state.privacy === 'invite' ? { invite: { pending: aud } } : { open: { banRanks: [], banned: [] } };
this.setState({
error: false,
success: true,
@ -139,10 +152,10 @@ export class NewScreen extends Component {
// we make a path of the form /~zod/cool-group
// if not, we make a path of the form /~/~zod/free-chat
let appPath = `/~${window.ship}${station}`;
if (!state.createGroup && state.groups.length === 0) {
appPath = `/~${appPath}`;
}
let groupPath = appPath;
// if (!state.createGroup && state.groups.length === 0) {
// appPath = `/~${appPath}`;
// }
let groupPath = `/ship${appPath}`;
if (state.groups.length > 0) {
groupPath = state.groups[0];
}
@ -151,9 +164,10 @@ export class NewScreen extends Component {
state.description,
appPath,
groupPath,
state.security,
policy,
aud,
state.allowHistory
state.allowHistory,
state.createGroup
);
submit.then(() => {
this.setState({ awaiting: false });
@ -164,12 +178,9 @@ export class NewScreen extends Component {
render() {
const { props, state } = this;
let inviteSwitchClasses = (state.security === 'village')
let privacySwitchClasses = (state.privacy === 'invite')
? 'relative checked bg-green2 br3 h1 toggle v-mid z-0'
: 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0';
if (state.createGroup) {
inviteSwitchClasses = inviteSwitchClasses + ' o-50';
}
const createGroupClasses = state.createGroup
? 'relative checked bg-green2 br3 h1 toggle v-mid z-0'
@ -211,8 +222,8 @@ export class NewScreen extends Component {
}
const groups = {};
Object.keys(props.permissions).forEach((pem) => {
groups[pem] = props.permissions[pem].who;
Object.keys(props.groups).forEach((pem) => {
groups[pem] = props.groups[pem].members;
});
return (
@ -271,6 +282,18 @@ export class NewScreen extends Component {
setInvite={this.setInvite}
/>
{createGroupToggle}
<div className="mv7">
<input
type="checkbox"
style={{ WebkitAppearance: 'none', width: 28 }}
className={privacySwitchClasses}
onChange={this.privacyChange}
/>
<span className="dib f9 white-d inter ml3">Private</span>
<p className="f9 gray2 pt1" style={{ paddingLeft: 40 }}>
Users will have to be invited to join
</p>
</div>
<button
onClick={this.onClickCreate.bind(this)}
className={createClasses}

View File

@ -185,10 +185,10 @@ export class SettingsScreen extends Component {
const chatOwner = (deSig(props.match.params.ship) === window.ship);
const groupPath = props.association['group-path'];
const ownedUnmanagedVillage =
chatOwner &&
props.station.slice(0, 3) === '/~/' &&
props.permission.kind === 'white';
!props.contacts[groupPath];
if (!ownedUnmanagedVillage) {
return null;

View File

@ -48,7 +48,18 @@ export class Sidebar extends Component {
const groupedChannels = {};
Object.keys(props.inbox).map((box) => {
if (box.startsWith('/~/')) {
const path = chatAssoc[box]
? chatAssoc[box]['group-path'] : box;
if (path in contactAssoc) {
if (groupedChannels[path]) {
const array = groupedChannels[path];
array.push(box);
groupedChannels[path] = array;
} else {
groupedChannels[path] = [box];
}
} else {
if (groupedChannels['/~/']) {
const array = groupedChannels['/~/'];
array.push(box);
@ -56,19 +67,6 @@ export class Sidebar extends Component {
} else {
groupedChannels['/~/'] = [box];
}
} else {
const path = chatAssoc[box]
? chatAssoc[box]['group-path'] : box;
if (path in contactAssoc) {
if (groupedChannels[path]) {
const array = groupedChannels[path];
array.push(box);
groupedChannels[path] = array;
} else {
groupedChannels[path] = [box];
}
}
}
});