Merge pull request #4345 from urbit/mp/profile/stubs

profile: more stubs
This commit is contained in:
L 2021-01-29 17:06:37 -06:00 committed by GitHub
commit 6927327c7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 109 additions and 86 deletions

View File

@ -1,6 +1,6 @@
/- spider,
graph=graph-store,
*metadata-store,
met=metadata-store,
*group,
group-store,
inv=invite-store
@ -57,18 +57,18 @@
::
:: Setup metadata
::
=/ =metadata
%* . *metadata
=/ =metadatum:met
%* . *metadatum:met
title title.action
description description.action
date-created now.bowl
creator our.bowl
module module.action
==
=/ =metadata-action
[%add group graph+rid.action metadata]
=/ met-action=action:met
[%add group graph+rid.action metadatum]
;< ~ bind:m
(poke-our %metadata-store %metadata-action !>(metadata-action))
(poke-our %metadata-store %metadata-action !>(met-action))
;< ~ bind:m
(poke-our %metadata-push-hook %push-hook-action !>([%add group]))
::

View File

@ -1,4 +1,4 @@
/- spider, graph-view, graph=graph-store, *metadata-store, *group
/- spider, graph-view, graph=graph-store, met=metadata-store, *group
/+ strandio, resource
=>
|%
@ -9,16 +9,14 @@
++ scry-metadata
|= rid=resource
=/ m (strand ,(unit resource))
;< paxs=(unit (set path)) bind:m
%+ scry:strandio ,(unit (set path))
;< group=(unit resource) bind:m
%+ scry:strandio ,(unit resource)
;: weld
/gx/metadata-store/resource/graph
(en-path:resource rid)
/noun
==
?~ paxs (pure:m ~)
?~ u.paxs (pure:m ~)
(pure:m `(de-path:resource n.u.paxs))
(pure:m group)
::
++ scry-group
|= rid=resource
@ -42,11 +40,7 @@
;< ~ bind:m
(poke-our %graph-push-hook %push-hook-action !>([%remove rid]))
;< ~ bind:m
%+ poke-our %metadata-hook
:- %metadata-action
!> :+ %remove
(en-path:resource group-rid)
[%graph (en-path:resource rid)]
(poke-our %metadata-push-hook %push-hook-action !>([%remove rid]))
(pure:m ~)
--
::
@ -74,6 +68,5 @@
(poke-our %group-push-hook %push-hook-action !>([%remove rid.action]))
;< ~ bind:m (delete-graph u.ugroup-rid rid.action)
;< ~ bind:m
%+ poke-our %metadata-hook
metadata-hook-action+!>([%remove (en-path:resource u.ugroup-rid)])
(poke-our %metadata-push-hook %push-hook-action !>([%remove rid.action]))
(pure:m !>(~))

View File

@ -22,6 +22,8 @@ export default class ContactsApi extends BaseApi<StoreState> {
{color: 'fff'} // with no 0x prefix
{avatar: null}
{avatar: ''}
{add-group: {ship, name}}
{remove-group: {ship, name}}
*/
console.log(ship, editField);
return this.storeAction({

View File

@ -116,7 +116,7 @@ export default function index(contacts, associations, apps, currentGroup, groups
title,
`/~landscape${group}/join/${app}${each.resource}`,
app.charAt(0).toUpperCase() + app.slice(1),
(associations?.contacts?.[each.group]?.metadata?.title || null)
(associations?.groups?.[each.group]?.metadata?.title || null)
);
subscriptions.push(obj);
}

View File

@ -8,7 +8,7 @@ export function getTitleFromWorkspace(
case "home":
return "DMs + Drafts";
case "group":
const association = associations.contacts[workspace.group];
const association = associations.groups[workspace.group];
return association?.metadata?.title || "";
}
}

View File

@ -18,7 +18,7 @@ export type Serial = string;
export type Jug<K,V> = Map<K,Set<V>>;
// name of app
export type AppName = 'chat' | 'link' | 'contacts' | 'publish' | 'graph';
export type AppName = 'contacts' | 'groups' | 'graph';
export function getTagFromFrond<O>(frond: O): keyof O {
const tags = Object.keys(frond) as Array<keyof O>;

View File

@ -16,7 +16,7 @@ const sortGroupsAlph = (a: Association, b: Association) =>
alphabeticalOrder(a.metadata.title, b.metadata.title);
const getGraphUnreads = (associations: Associations, unreads: Unreads) => (path: string) =>
const getGraphUnreads = (associations: Associations, unreads: Unreads) => (path: string) =>
f.flow(
f.pickBy((a: Association) => a.group === path),
f.map('resource'),
@ -24,7 +24,7 @@ const getGraphUnreads = (associations: Associations, unreads: Unreads) => (path:
f.reduce(f.add, 0)
)(associations.graph);
const getGraphNotifications = (associations: Associations, unreads: Unreads) => (path: string) =>
const getGraphNotifications = (associations: Associations, unreads: Unreads) => (path: string) =>
f.flow(
f.pickBy((a: Association) => a.group === path),
f.map('resource'),
@ -36,7 +36,7 @@ const getGraphNotifications = (associations: Associations, unreads: Unreads) =>
export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
const { associations, unreads, inbox, ...boxProps } = props;
const groups = Object.values(associations?.contacts || {})
const groups = Object.values(associations?.groups || {})
.filter((e) => e?.group in props.groups)
.sort(sortGroupsAlph);
const graphUnreads = getGraphUnreads(associations || {}, unreads);
@ -78,10 +78,10 @@ function Group(props: GroupProps) {
<Col height="100%" justifyContent="space-between">
<Text>{title}</Text>
<Col>
{unreads > 0 &&
{unreads > 0 &&
(<Text gray>{unreads} unread{unreads !== 1 && 's'} </Text>)
}
{updates > 0 &&
{updates > 0 &&
(<Text mt="1" color="blue">{updates} update{updates !== 1 && 's'} </Text>)
}
</Col>

View File

@ -63,7 +63,7 @@ export function Header(props: {
const time = moment(props.time).format("HH:mm");
const groupTitle =
props.associations.contacts?.[props.group]?.metadata?.title;
props.associations.groups?.[props.group]?.metadata?.title;
const app = props.chat ? 'chat' : 'graph';
const channelTitle =

View File

@ -31,7 +31,7 @@ export function Invites(props: InvitesProps) {
const resourcePath = resourceAsPath(invite.resource);
if (app === "contacts") {
await api.contacts.join(resource);
await waiter((p) => resourcePath in p.associations?.contacts);
await waiter((p) => resourcePath in p.associations?.groups);
await api.invite.accept(app, uid);
history.push(`/~landscape${resourcePath}`);
} else if (app === "graph") {

View File

@ -44,7 +44,7 @@ export default function NotificationsScreen(props: any) {
filter.groups.length === 0
? "All"
: filter.groups
.map((g) => props.associations?.contacts?.[g]?.metadata?.title)
.map((g) => props.associations?.groups?.[g]?.metadata?.title)
.join(", ");
return (
<Switch>

View File

@ -21,6 +21,8 @@ import { AsyncButton } from "~/views/components/AsyncButton";
import { ColorInput } from "~/views/components/ColorInput";
import { ImageInput } from "~/views/components/ImageInput";
import { MarkdownField } from "~/views/apps/publish/components/MarkdownField";
import { resourceFromPath } from "~/logic/lib/group";
import GroupSearch from "~/views/components/GroupSearch";
const formSchema = Yup.object({
@ -62,8 +64,15 @@ export function EditProfile(props: any) {
return acc.then(() =>
api.contacts.setPublic(newValue)
);
} else if (key === 'groups') {
newValue.map((e) => {
if (!contact['groups']?.[e]) {
return acc.then(() => {
api.contacts.edit(ship, { 'add-group': resourceFromPath(e) });
});
}
})
} else if (
key !== "groups" &&
key !== "last-updated" &&
key !== "isPublic"
) {
@ -93,7 +102,7 @@ export function EditProfile(props: any) {
<Input id="nickname" label="Name" mb={3} />
<Col width="100%">
<Text mb={2}>Description</Text>
<MarkdownField id="bio" mb={3} s3={props.s3} />
<MarkdownField id="bio" mb={3} s3={props.s3} />
</Col>
<ColorInput id="color" label="Sigil Color" mb={3} />
<Row mb={3} width="100%">
@ -105,7 +114,8 @@ export function EditProfile(props: any) {
</Col>
</Row>
<Checkbox mb={3} id="isPublic" label="Public Profile" />
<AsyncButton primary loadingText="Updating..." border>
<GroupSearch label="Pinned Groups" id="groups" groups={props.groups} associations={props.associations} />
<AsyncButton primary loadingText="Updating..." border mt={3}>
Submit
</AsyncButton>
</Form>

View File

@ -66,6 +66,8 @@ export function Profile(props: any) {
contact={contact}
s3={props.s3}
api={props.api}
groups={props.groups}
associations={props.associations}
isPublic={isPublic}/>
) : (
<ViewProfile ship={ship} contact={contact} isPublic={isPublic} />

View File

@ -45,6 +45,8 @@ export default function ProfileScreen(props: any) {
<Box>
<Profile
ship={ship}
associations={props.associations}
groups={props.groups}
contact={contact}
api={props.api}
s3={props.s3}

View File

@ -1,4 +1,4 @@
import React, { useMemo, useCallback } from "react";
import React, { useMemo, useCallback, useState, useEffect } from 'react';
import {
Box,
Text,
@ -6,17 +6,17 @@ import {
Row,
Col,
Icon,
ErrorLabel,
} from "@tlon/indigo-react";
import _ from "lodash";
import { useField } from "formik";
import styled from "styled-components";
ErrorLabel
} from '@tlon/indigo-react';
import _ from 'lodash';
import { useField } from 'formik';
import styled from 'styled-components';
import { roleForShip } from "~/logic/lib/group";
import { roleForShip } from '~/logic/lib/group';
import { DropdownSearch } from "./DropdownSearch";
import { Groups } from "~/types";
import { Associations, Association } from "~/types/metadata-update";
import { DropdownSearch } from './DropdownSearch';
import { Groups } from '~/types';
import { Associations, Association } from '~/types/metadata-update';
interface InviteSearchProps {
disabled?: boolean;
@ -26,11 +26,12 @@ interface InviteSearchProps {
label: string;
caption?: string;
id: string;
maxLength?: number;
}
const CandidateBox = styled(Box)<{ selected: boolean }>`
&:hover {
background-color: ${(p) => p.theme.colors.washedGray};
background-color: ${p => p.theme.colors.washedGray};
}
`;
@ -64,38 +65,45 @@ function renderCandidate(
export function GroupSearch(props: InviteSearchProps) {
const { id, caption, label } = props;
const [selected, setSelected] = useState([] as string[]);
const groups: Association[] = useMemo(() => {
return props.adminOnly
? Object.values(
Object.keys(props.associations?.contacts)
Object.keys(props.associations?.groups)
.filter(
(e) => roleForShip(props.groups[e], window.ship) === "admin"
e => roleForShip(props.groups[e], window.ship) === 'admin'
)
.reduce((obj, key) => {
obj[key] = props.associations?.contacts[key];
obj[key] = props.associations?.groups[key];
return obj;
}, {}) || {}
)
: Object.values(props.associations?.contacts || {});
}, [props.associations?.contacts]);
: Object.values(props.associations?.groups || {});
}, [props.associations?.groups]);
const [{ value }, meta, { setValue, setTouched }] = useField(props.id);
useEffect(() => {
setValue(selected);
}, [selected])
const { title: groupTitle } =
props.associations.contacts?.[value]?.metadata || {};
props.associations.groups?.[value]?.metadata || {};
const onSelect = useCallback(
(a: Association) => {
setValue(a.group);
(s: string) => {
setTouched(true);
setSelected(v => _.uniq([...v, s]));
},
[setValue]
[setTouched, setSelected]
);
const onUnselect = useCallback(() => {
setValue(undefined);
setTouched(true);
}, [setValue]);
const onRemove = useCallback(
(s: string) => {
setSelected(groups => groups.filter(group => group !== s))
},
[setSelected]
);
return (
<Col>
@ -105,25 +113,11 @@ export function GroupSearch(props: InviteSearchProps) {
{caption}
</Label>
)}
{value && (
<Row
borderRadius="1"
mt="2"
width="fit-content"
border="1"
borderColor="gray"
height="32px"
px="2"
alignItems="center"
>
<Text mr="2">{groupTitle || value}</Text>
<Icon onClick={onUnselect} icon="X" />
</Row>
)}
{!value && (
<DropdownSearch<Association>
mt="2"
candidates={groups}
placeholder="Search for groups..."
disabled={props.maxLength ? selected.length >= props.maxLength : false}
renderCandidate={renderCandidate}
search={(s: string, a: Association) =>
a.metadata.title.toLowerCase().startsWith(s.toLowerCase())
@ -131,8 +125,27 @@ export function GroupSearch(props: InviteSearchProps) {
getKey={(a: Association) => a.group}
onSelect={onSelect}
/>
{value?.length > 0 && (
value.map((e) => {
return (
<Row
key={e}
borderRadius="1"
mt="2"
width="fit-content"
border="1"
borderColor="gray"
height="32px"
px="2"
alignItems="center"
>
<Text mr="2">{groupTitle || e}</Text>
<Icon onClick={onRemove} icon="X" />
</Row>
);
})
)}
<ErrorLabel hasError={!!(meta.touched && meta.error)}>
<ErrorLabel hasError={Boolean(meta.touched && meta.error)}>
{meta.error}
</ErrorLabel>
</Col>

View File

@ -173,7 +173,7 @@ export function ShipSearch(props: InviteSearchProps) {
const result = ob.isValidPatp(ship);
return result ? deSig(s) ?? undefined : undefined;
}}
placeholder="Search for ships"
placeholder="Search for ships..."
candidates={peers}
renderCandidate={renderCandidate}
disabled={props.maxLength ? selected.length >= props.maxLength : false}

View File

@ -42,9 +42,9 @@ function RecentGroups(props: { recent: string[]; associations: Associations }) {
Recent Groups
</Box>
{props.recent.filter((e) => {
return (e in associations?.contacts);
return (e in associations?.groups);
}).slice(1, 5).map((g) => {
const assoc = associations.contacts[g];
const assoc = associations.groups[g];
const color = uxToHex(assoc?.metadata?.color || '0x0');
return (
<Link key={g} style={{ minWidth: 0 }} to={`/~landscape${g}`}>
@ -78,7 +78,7 @@ export function GroupSwitcher(props: {
}) {
const { associations, workspace, isAdmin } = props;
const title = getTitleFromWorkspace(associations, workspace);
const metadata = workspace.type === 'home' ? undefined : associations.contacts[workspace.group].metadata;
const metadata = workspace.type === 'home' ? undefined : associations.groups[workspace.group].metadata;
const navTo = (to: string) => `${props.baseUrl}${to}`;
return (
<Row width="100%" alignItems="center" height='48px' backgroundColor="white" zIndex="2" position="sticky" top="0px" pl='3' borderBottom='1px solid' borderColor='washedGray'>

View File

@ -14,7 +14,7 @@ const formSchema = Yup.object({
});
interface FormSchema {
group: string | null;
group: string[] | null;
}
interface GroupifyFormProps {
@ -37,7 +37,7 @@ export function GroupifyForm(props: GroupifyFormProps) {
await props.api.graph.groupifyGraph(
ship,
name,
values.group || undefined
values.group?.toString() || undefined
);
const mod = association.metadata.module || association['app-name'];
const newGroup = values.group || association.group;
@ -79,6 +79,7 @@ export function GroupifyForm(props: GroupifyFormProps) {
groups={props.groups}
associations={props.associations}
adminOnly
maxLength={1}
/>
<AsyncButton primary loadingText="Groupifying..." border>
Groupify

View File

@ -43,7 +43,7 @@ export function GroupsPane(props: GroupsPaneProps) {
const groupContacts = (groupPath && contacts[groupPath]) || undefined;
const rootIdentity = contacts?.["/~/default"]?.[window.ship];
const groupAssociation =
(groupPath && associations.contacts[groupPath]) || undefined;
(groupPath && associations.groups[groupPath]) || undefined;
const group = (groupPath && groups[groupPath]) || undefined;
const [recentGroups, setRecentGroups] = useLocalStorageState<string[]>(
"recent-groups",
@ -196,7 +196,7 @@ export function GroupsPane(props: GroupsPaneProps) {
let summary: ReactNode;
if(groupAssociation?.group) {
const memberCount = props.groups[groupAssociation.group].members.size;
summary = <GroupSummary
summary = <GroupSummary
memberCount={memberCount}
channelCount={0}
metadata={groupAssociation.metadata}

View File

@ -65,7 +65,7 @@ export function NewGroup(props: NewGroupProps & RouteComponentProps) {
await api.contacts.create(name, policy, title, description);
const path = `/ship/~${window.ship}/${name}`;
await waiter(({ contacts, groups, associations }) => {
return path in contacts && path in groups && path in associations.contacts;
return path in contacts && path in groups && path in associations.groups;
});
actions.setStatus({ success: null });

View File

@ -37,8 +37,8 @@ export function Resource(props: ResourceProps) {
const skelProps = { api, association };
let title = props.association.metadata.title;
if ('workspace' in props) {
if ('group' in props.workspace && props.workspace.group in props.associations.contacts) {
title = `${props.associations.contacts[props.workspace.group].metadata.title} - ${props.association.metadata.title}`;
if ('group' in props.workspace && props.workspace.group in props.associations.groups) {
title = `${props.associations.groups[props.workspace.group].metadata.title} - ${props.association.metadata.title}`;
}
}
return (

View File

@ -57,7 +57,7 @@ export function SidebarList(props: {
const assoc = associations[a];
return group
? assoc.group === group
: !(assoc.group in props.associations.contacts);
: !(assoc.group in props.associations.groups);
})
.sort(sidebarSort(associations, props.apps)[config.sortBy]);