Merge pull request #4362 from urbit/la/share-interface

contacts: fully integrated share interface wip
This commit is contained in:
L 2021-02-02 19:14:58 -06:00 committed by GitHub
commit 1c24043e6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 182 additions and 29 deletions

View File

@ -3,7 +3,7 @@
:: data store that holds individual contact data
::
/- store=contact-store, *resource
/+ default-agent, dbug, *migrate
/+ default-agent, dbug, *migrate, contact
|%
+$ card card:agent:gall
+$ state-4
@ -29,6 +29,7 @@
|_ =bowl:gall
+* this .
def ~(. (default-agent this %|) bowl)
con ~(. contact bowl)
::
++ on-init
=. rolodex (~(put by rolodex) our.bowl *contact:store)
@ -216,6 +217,16 @@
::
[%x %allowed-groups ~]
``noun+!>(allowed-groups)
::
[%x %is-allowed @ @ @ @ ~]
=/ is-personal =(i.t.t.t.t.t.path 'true')
=/ =resource
?: is-personal
[our.bowl %'']
[(slav %p i.t.t.path) i.t.t.t.path]
=/ =ship (slav %p i.t.t.t.t.path)
``json+!>(`json`b+(is-allowed:con resource ship))
==
::
++ on-leave on-leave:def

View File

@ -175,6 +175,10 @@
%share-co
?> ?=(%poke-ack -.sign)
(ack +.sign)
::
%push-co
?> ?=(%poke-ack -.sign)
(ack +.sign)
::
%md
?+ -.sign !!
@ -200,6 +204,7 @@
%- zing
:~ [(poke-our:(jn-pass-io /pull-md) %metadata-pull-hook cag)]~
[(poke-our:(jn-pass-io /pull-co) %contact-pull-hook cag)]~
::
?. scry-is-public:con ~
:_ ~
%+ poke:(jn-pass-io /share-co)

View File

@ -173,4 +173,10 @@
==
--
--
::
++ share-dejs
=, dejs:format
|%
++ share (ot [%share (su ;~(pfix sig fed:ag))]~)
--
--

View File

@ -70,20 +70,30 @@
|= [rid=resource =ship]
^- ?
=/ grp ~(. group bowl)
=/ shp (scry-for ? /allowed-ship/(scot %p ship))
?: ?& scry-is-public
=(rid [our.bowl %''])
==
%.y
?: shp %.y
?: ?& (~(has in scry-sharing) rid)
=/ allowed-groups (scry-for (set resource) /allowed-groups)
?| :: if they are requesting our personal profile, check if we are
:: either public, or if they are on the allowed-ships list.
:: this is used for direct messages and leap searches
::
?& =(rid [our.bowl %''])
?| :: if our profile is public, allow
::
scry-is-public
:: if the requester is an allowed-ship, allow
::
(scry-for ? /allowed-ship/(scot %p ship))
:: if the requester of our profile is the host of one of
:: our allowed-groups, allow
::
%+ lien ~(tap in allowed-groups)
|= res=resource
=(entity.res ship)
== ==
:: if they are requesting our contact data within a group,
:: we make sure that we are sharing that group,
:: and that they are a member of the group
::
?& (~(has in scry-sharing) rid)
(~(has in (members:grp rid)) ship)
==
%.y
=/ allowed-groups ~(tap in (scry-for (set resource) /allowed-groups))
|-
?~ allowed-groups %.n
?: (~(has in (members:grp i.allowed-groups)) ship)
%.y
$(allowed-groups t.allowed-groups)
== ==
--

View File

@ -1,3 +1,5 @@
/+ *contact-store
::
|_ share=[%share =ship]
++ grad %noun
++ grow
@ -7,6 +9,7 @@
::
++ grab
|%
++ noun share
++ noun _share
++ json share:share-dejs
--
--

View File

@ -57,6 +57,7 @@ export default class BaseApi<S extends object = {}> {
}
scry<T>(app: string, path: Path): Promise<T> {
console.log(path);
return fetch(`/~/scry/${app}${path}.json`).then(r => r.json() as Promise<T>);
}

View File

@ -34,12 +34,35 @@ export default class ContactsApi extends BaseApi<StoreState> {
});
}
allow(ship) {
return this.storeAction({
allow: ship
});
}
setPublic(setPublic: any) {
return this.storeAction({
'set-public': setPublic
});
}
share(recipient, us) {
return this.action(
'contact-push-hook',
'contact-share',
{ share: us },
recipient
);
}
fetchIsAllowed(entity, name, ship, personal) {
const isPersonal = personal ? 'true' : 'false';
return this.scry<any>(
'contact-store',
`/is-allowed/${entity}/${name}/${ship}/${isPersonal}`
);
}
private storeAction(action: any): Promise<any> {
return this.action('contact-store', 'contact-update', action)
}

View File

@ -1,4 +1,4 @@
import React, { useRef, useCallback, useEffect } from 'react';
import React, { useRef, useCallback, useEffect, useState } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { Col } from '@tlon/indigo-react';
import _ from 'lodash';
@ -35,6 +35,7 @@ export function ChatResource(props: ChatResourceProps) {
const [,, owner, name] = station.split('/');
const ourContact = contacts?.[`~${window.ship}`];
console.log(contacts);
const chatInput = useRef<ChatInput>();
@ -86,7 +87,15 @@ export function ChatResource(props: ChatResourceProps) {
return (
<Col {...bind} height="100%" overflow="hidden" position="relative">
<ShareProfile our={ourContact} />
<ShareProfile
our={ourContact}
api={props.api}
recipient={owner}
group={group}
groupPath={groupPath}
hideBanner={() => {
setProfileAllowed(true);
}} />
{dragging && <SubmitDragger />}
<ChatWindow
mailboxSize={5}

View File

@ -1,16 +1,100 @@
import React from 'react';
import React, {
useState,
useEffect
} from 'react';
import { Box, Row, Text, BaseImage } from '@tlon/indigo-react';
import { uxToHex } from '~/logic/lib/util';
import { Sigil } from '~/logic/lib/sigil';
export const ShareProfile = (props) => {
const image = (props?.our?.avatar)
? <BaseImage src={props.our.avatar} width='24px' height='24px' borderRadius={2} style={{ objectFit: 'cover' }} />
: <Row p={1} alignItems="center" borderRadius={2} backgroundColor={`#${uxToHex(props?.our?.color)}` || "#000000"}>
<Sigil ship={window.ship} size={16} icon color={`#${uxToHex(props?.our?.color)}` || "#000000"} />
</Row>;
const pathAsResource = (path) => {
if (!path) {
return false;
}
const pathArr = path.split('/');
if (pathArr.length !== 4) {
return false;
}
return (
return {
entity: pathArr[2],
name: pathArr[3]
};
};
export const ShareProfile = (props) => {
const { api, recipient, hideBanner, group, groupPath } = props;
console.log(groupPath);
// TODO: use isContactPublic somewhere
const [showBanner, setShowBanner] = useState(false);
const res = pathAsResource(groupPath);
useEffect(() => {
if (!res) { return; }
if (!group) { return; }
console.log(group);
if (group.hidden) {
// TODO:
// take the union of the pending set and the members set,
// subtract ourselves, then if *anyone* has not already been shared with,
// show the banner
// Promise.all all the members of the set
let check =
Promise.all()
props.api.contacts.fetchIsAllowed(
`~${window.ship}`,
'personal', // not used
recipient,
true
).then((retVal) => {
console.log(retVal);
setShowBanner(!retVal);
});
} else {
// TODO:
// if the group is not in the allowed-groups set, then show the banner
props.api.contacts.fetchIsAllowed(
res.entity,
res.name,
recipient,
false
).then((retVal) => {
console.log(retVal);
setShowBanner(!retVal);
});
}
}, [recipient, res, group]);
const image = (props?.our?.avatar)
? (
<BaseImage
src={props.our.avatar}
width='24px'
height='24px'
borderRadius={2}
style={{ objectFit: 'cover' }} />
) : (
<Row
p={1}
alignItems="center"
borderRadius={2}
backgroundColor={!props.our ? `#${uxToHex(props.our.color)}` : "#000000"}>
<Sigil
ship={window.ship}
size={16}
color={`#${uxToHex(props?.our?.color)}` || "#000000"}
icon />
</Row>
);
const onClick = () => {
api.contacts.allow(recipient).then(() => {
api.contacts.share(recipient, window.ship);
});
hideBanner();
};
return showBanner ? (
<Row
height="48px"
alignItems="center"
@ -22,9 +106,9 @@ export const ShareProfile = (props) => {
{image}
<Text verticalAlign="middle" pl={2}>Share private profile?</Text>
</Row>
<Box pr={2}>
<Box pr={2} onClick={onClick}>
<Text color="blue" bold cursor="pointer">Share</Text>
</Box>
</Row>
);
) : null;
};

View File

@ -59,3 +59,4 @@ export function SetStatus(props: any) {
</Row>
);
}