interface: covert contact store to zustand

This commit is contained in:
Tyler Brown Cifu Shuster 2021-02-25 19:46:45 -08:00
parent 72aa7f5aee
commit 12645644f7
53 changed files with 166 additions and 155 deletions

View File

@ -2,8 +2,9 @@ import { useEffect, useState } from 'react';
import _ from 'lodash'; import _ from 'lodash';
import f, { memoize } from 'lodash/fp'; import f, { memoize } from 'lodash/fp';
import bigInt, { BigInteger } from 'big-integer'; import bigInt, { BigInteger } from 'big-integer';
import { Contact } from '@urbit/api'; import { Association, Contact } from '@urbit/api';
import useLocalState from '../state/local'; import useLocalState from '../state/local';
import produce from 'immer';
export const MOBILE_BROWSER_REGEX = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i; export const MOBILE_BROWSER_REGEX = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i;
@ -408,3 +409,8 @@ export function getItemTitle(association: Association) {
} }
return association.metadata.title || association.resource; return association.metadata.title || association.resource;
} }
export const stateSetter = <StateType>(fn: (state: StateType) => void, set): void => {
// TODO this is a stub for the store debugging
return set(produce(fn));
};

View File

@ -1,44 +1,53 @@
import _ from 'lodash'; import _ from 'lodash';
import { StoreState } from '../../store/type'; import { compose } from 'lodash/fp';
import { Cage } from '~/types/cage';
import { ContactUpdate } from '@urbit/api/contacts';
import { resourceAsPath } from '../lib/util';
type ContactState = Pick<StoreState, 'contacts'>; import { ContactUpdate } from '@urbit/api';
export const ContactReducer = (json, state) => { import useContactState, { ContactState } from '../state/contacts';
const data = _.get(json, 'contact-update', false);
export const ContactReducer = (json) => {
const data: ContactUpdate = _.get(json, 'contact-update', false);
if (data) { if (data) {
initial(data, state); useContactState.setState(
add(data, state); compose([
remove(data, state); initial,
edit(data, state); add,
setPublic(data, state); remove,
edit,
setPublic
].map(reducer => reducer.bind(reducer, data))
)(useContactState.getState())
);
} }
// TODO: better isolation // TODO: better isolation
const res = _.get(json, 'resource', false); const res = _.get(json, 'resource', false);
if(res) { if (res) {
state.nackedContacts = state.nackedContacts.add(`~${res.ship}`); useContactState.setState({
nackedContacts: useContactState.getState().nackedContacts.add(`~${res.ship}`)
});
} }
}; };
const initial = (json: ContactUpdate, state: S) => { const initial = (json: ContactUpdate, state: ContactState): ContactState => {
const data = _.get(json, 'initial', false); const data = _.get(json, 'initial', false);
if (data) { if (data) {
state.contacts = data.rolodex; state.contacts = data.rolodex;
state.isContactPublic = data['is-public']; state.isContactPublic = data['is-public'];
} }
return state;
}; };
const add = (json: ContactUpdate, state: S) => { const add = (json: ContactUpdate, state: ContactState): ContactState => {
const data = _.get(json, 'add', false); const data = _.get(json, 'add', false);
if (data) { if (data) {
state.contacts[data.ship] = data.contact; state.contacts[data.ship] = data.contact;
} }
return state;
}; };
const remove = (json: ContactUpdate, state: S) => { const remove = (json: ContactUpdate, state: ContactState): ContactState => {
const data = _.get(json, 'remove', false); const data = _.get(json, 'remove', false);
if ( if (
data && data &&
@ -46,9 +55,10 @@ const remove = (json: ContactUpdate, state: S) => {
) { ) {
delete state.contacts[data.ship]; delete state.contacts[data.ship];
} }
return state;
}; };
const edit = (json: ContactUpdate, state: S) => { const edit = (json: ContactUpdate, state: ContactState): ContactState => {
const data = _.get(json, 'edit', false); const data = _.get(json, 'edit', false);
const ship = `~${data.ship}`; const ship = `~${data.ship}`;
if ( if (
@ -57,7 +67,7 @@ const edit = (json: ContactUpdate, state: S) => {
) { ) {
const [field] = Object.keys(data['edit-field']); const [field] = Object.keys(data['edit-field']);
if (!field) { if (!field) {
return; return state;
} }
const value = data['edit-field'][field]; const value = data['edit-field'][field];
@ -71,10 +81,12 @@ const edit = (json: ContactUpdate, state: S) => {
state.contacts[ship][field] = value; state.contacts[ship][field] = value;
} }
} }
return state;
}; };
const setPublic = (json: ContactUpdate, state: S) => { const setPublic = (json: ContactUpdate, state: ContactState): ContactState => {
const data = _.get(json, 'set-public', state.isContactPublic); const data = _.get(json, 'set-public', state.isContactPublic);
state.isContactPublic = data; state.isContactPublic = data;
return state;
}; };

View File

@ -0,0 +1,51 @@
import React from "react";
import create, { State } from 'zustand';
import { persist } from 'zustand/middleware';
import { Patp, Rolodex, Scry } from "@urbit/api";
import { stateSetter } from "~/logic/lib/util";
// import useApi from "~/logic/lib/useApi";
export interface ContactState extends State {
contacts: Rolodex;
isContactPublic: boolean;
nackedContacts: Set<Patp>;
// fetchIsAllowed: (entity, name, ship, personal) => Promise<boolean>;
set: (fn: (state: ContactState) => void) => void;
};
const useContactState = create<ContactState>(persist((set, get) => ({
contacts: {},
nackedContacts: new Set(),
isContactPublic: false,
// fetchIsAllowed: async (
// entity,
// name,
// ship,
// personal
// ): Promise<boolean> => {
// const isPersonal = personal ? 'true' : 'false';
// const api = useApi();
// return api.scry({
// app: 'contact-store',
// path: `/is-allowed/${entity}/${name}/${ship}/${isPersonal}`
// });
// },
set: fn => stateSetter(fn, set)
}), {
name: 'LandscapeContactState'
}));
function withContactState<P, S extends keyof ContactState>(Component: any, stateMemberKeys?: S[]) {
return React.forwardRef((props: Omit<P, S>, ref) => {
const contactState = stateMemberKeys ? useContactState(
state => stateMemberKeys.reduce(
(object, key) => ({ ...object, [key]: state[key] }), {}
)
): useContactState();
return <Component ref={ref} {...contactState} {...props} />
});
}
export { useContactState as default, withContactState };

View File

@ -77,9 +77,6 @@ export default class GlobalStore extends BaseStore<StoreState> {
}, },
credentials: null credentials: null
}, },
isContactPublic: false,
contacts: {},
nackedContacts: new Set(),
notifications: new BigIntOrderedMap<Timebox>(), notifications: new BigIntOrderedMap<Timebox>(),
archivedNotifications: new BigIntOrderedMap<Timebox>(), archivedNotifications: new BigIntOrderedMap<Timebox>(),
notificationsGroupConfig: [], notificationsGroupConfig: [],
@ -113,7 +110,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
this.connReducer.reduce(data, this.state); this.connReducer.reduce(data, this.state);
GraphReducer(data, this.state); GraphReducer(data, this.state);
HarkReducer(data, this.state); HarkReducer(data, this.state);
ContactReducer(data, this.state); ContactReducer(data);
this.settingsReducer.reduce(data, this.state); this.settingsReducer.reduce(data, this.state);
GroupViewReducer(data, this.state); GroupViewReducer(data, this.state);
} }

View File

@ -25,12 +25,9 @@ export interface StoreState {
invites: Invites; invites: Invites;
// metadata state // metadata state
associations: Associations; associations: Associations;
// contact state
contacts: Rolodex;
// groups state // groups state
groups: Groups; groups: Groups;
groupKeys: Set<Path>; groupKeys: Set<Path>;
nackedContacts: Set<Patp>
s3: S3State; s3: S3State;
graphs: Graphs; graphs: Graphs;
graphKeys: Set<string>; graphKeys: Set<string>;

View File

@ -28,6 +28,7 @@ import GlobalApi from '~/logic/api/global';
import { uxToHex } from '~/logic/lib/util'; import { uxToHex } from '~/logic/lib/util';
import { foregroundFromBackground } from '~/logic/lib/sigil'; import { foregroundFromBackground } from '~/logic/lib/sigil';
import { withLocalState } from '~/logic/state/local'; import { withLocalState } from '~/logic/state/local';
import { withContactState } from '~/logic/state/contacts';
const Root = styled.div` const Root = styled.div`
@ -116,8 +117,8 @@ class App extends React.Component {
faviconString() { faviconString() {
let background = '#ffffff'; let background = '#ffffff';
if (this.state.contacts.hasOwnProperty('/~/default')) { if (this.props.contacts.hasOwnProperty('/~/default')) {
background = `#${uxToHex(this.state.contacts['/~/default'][window.ship].color)}`; background = `#${uxToHex(this.props.contacts['/~/default'][window.ship].color)}`;
} }
const foreground = foregroundFromBackground(background); const foreground = foregroundFromBackground(background);
const svg = sigiljs({ const svg = sigiljs({
@ -139,7 +140,7 @@ class App extends React.Component {
const notificationsCount = state.notificationsCount || 0; const notificationsCount = state.notificationsCount || 0;
const doNotDisturb = state.doNotDisturb || false; const doNotDisturb = state.doNotDisturb || false;
const ourContact = this.state.contacts[`~${this.ship}`] || null; const ourContact = this.props.contacts[`~${this.ship}`] || null;
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
@ -171,7 +172,6 @@ class App extends React.Component {
apps={state.launch} apps={state.launch}
tiles={state.launch.tiles} tiles={state.launch.tiles}
api={this.api} api={this.api}
contacts={state.contacts}
notifications={state.notificationsCount} notifications={state.notificationsCount}
invites={state.invites} invites={state.invites}
groups={state.groups} groups={state.groups}
@ -195,5 +195,5 @@ class App extends React.Component {
} }
} }
export default withLocalState(process.env.NODE_ENV === 'production' ? App : hot(App)); export default withContactState(withLocalState(process.env.NODE_ENV === 'production' ? App : hot(App)));

View File

@ -17,6 +17,7 @@ import useS3 from '~/logic/lib/useS3';
import { isWriter, resourceFromPath } from '~/logic/lib/group'; import { isWriter, resourceFromPath } from '~/logic/lib/group';
import './css/custom.css'; import './css/custom.css';
import useContactState from '~/logic/state/contacts';
type ChatResourceProps = StoreState & { type ChatResourceProps = StoreState & {
association: Association; association: Association;
@ -28,7 +29,7 @@ export function ChatResource(props: ChatResourceProps) {
const station = props.association.resource; const station = props.association.resource;
const groupPath = props.association.group; const groupPath = props.association.group;
const group = props.groups[groupPath]; const group = props.groups[groupPath];
const contacts = props.contacts; const contacts = useContactState(state => state.contacts);
const graph = props.graphs[station.slice(7)]; const graph = props.graphs[station.slice(7)];
const isChatMissing = !props.graphKeys.has(station.slice(7)); const isChatMissing = !props.graphKeys.has(station.slice(7));
const unreadCount = props.unreads.graph?.[station]?.['/']?.unreads || 0; const unreadCount = props.unreads.graph?.[station]?.['/']?.unreads || 0;

View File

@ -18,7 +18,6 @@ type ChatInputProps = IuseS3 & {
station: unknown; station: unknown;
ourContact: unknown; ourContact: unknown;
envelopes: Envelope[]; envelopes: Envelope[];
contacts: Contacts;
onUnmount(msg: string): void; onUnmount(msg: string): void;
s3: unknown; s3: unknown;
placeholder: string; placeholder: string;

View File

@ -33,6 +33,7 @@ import { Mention } from '~/views/components/MentionText';
import styled from 'styled-components'; import styled from 'styled-components';
import useLocalState from '~/logic/state/local'; import useLocalState from '~/logic/state/local';
import Timestamp from '~/views/components/Timestamp'; import Timestamp from '~/views/components/Timestamp';
import useContactState from '~/logic/state/contacts';
export const DATESTAMP_FORMAT = '[~]YYYY.M.D'; export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
@ -85,7 +86,6 @@ interface ChatMessageProps {
isLastRead: boolean; isLastRead: boolean;
group: Group; group: Group;
association: Association; association: Association;
contacts: Contacts;
className?: string; className?: string;
isPending: boolean; isPending: boolean;
style?: unknown; style?: unknown;
@ -120,7 +120,6 @@ export default class ChatMessage extends Component<ChatMessageProps> {
isLastRead, isLastRead,
group, group,
association, association,
contacts,
className = '', className = '',
isPending, isPending,
style, style,
@ -164,7 +163,6 @@ export default class ChatMessage extends Component<ChatMessageProps> {
const messageProps = { const messageProps = {
msg, msg,
timestamp, timestamp,
contacts,
association, association,
group, group,
measure: reboundMeasure.bind(this), measure: reboundMeasure.bind(this),
@ -219,7 +217,6 @@ export default class ChatMessage extends Component<ChatMessageProps> {
export const MessageAuthor = ({ export const MessageAuthor = ({
timestamp, timestamp,
contacts,
msg, msg,
measure, measure,
group, group,
@ -231,6 +228,7 @@ export const MessageAuthor = ({
...rest ...rest
}) => { }) => {
const dark = useLocalState((state) => state.dark); const dark = useLocalState((state) => state.dark);
const contacts = useContactState(state => state.contacts);
const datestamp = moment const datestamp = moment
.unix(msg['time-sent'] / 1000) .unix(msg['time-sent'] / 1000)
@ -364,7 +362,6 @@ export const MessageAuthor = ({
export const Message = ({ export const Message = ({
timestamp, timestamp,
contacts,
msg, msg,
measure, measure,
group, group,
@ -376,6 +373,7 @@ export const Message = ({
...rest ...rest
}) => { }) => {
const { hovering, bind } = useHovering(); const { hovering, bind } = useHovering();
const contacts = useContactState(state => state.contacts);
return ( return (
<Box position='relative' {...rest}> <Box position='relative' {...rest}>
{timestampHover ? ( {timestampHover ? (

View File

@ -32,7 +32,6 @@ type ChatWindowProps = RouteComponentProps<{
}> & { }> & {
unreadCount: number; unreadCount: number;
graph: Graph; graph: Graph;
contacts: Contacts;
association: Association; association: Association;
group: Group; group: Group;
ship: Patp; ship: Patp;
@ -240,11 +239,8 @@ export default class ChatWindow extends Component<
const { const {
unreadCount, unreadCount,
api, api,
ship,
station,
association, association,
group, group,
contacts,
graph, graph,
history, history,
groups, groups,
@ -255,7 +251,6 @@ export default class ChatWindow extends Component<
const messageProps = { const messageProps = {
association, association,
group, group,
contacts,
unreadMarkerRef, unreadMarkerRef,
history, history,
api, api,

View File

@ -7,7 +7,6 @@ import { deSig } from '~/logic/lib/util';
export default class GraphApp extends PureComponent { export default class GraphApp extends PureComponent {
render() { render() {
const { props } = this; const { props } = this;
const contacts = props.contacts ? props.contacts : {};
const groups = props.groups ? props.groups : {}; const groups = props.groups ? props.groups : {};
const associations = const associations =
props.associations ? props.associations : { graph: {}, contacts: {} }; props.associations ? props.associations : { graph: {}, contacts: {} };

View File

@ -7,7 +7,7 @@ import _ from 'lodash';
import { Col, Button, Box, Row, Icon, Text } from '@tlon/indigo-react'; import { Col, Button, Box, Row, Icon, Text } from '@tlon/indigo-react';
import './css/custom.css'; import './css/custom.css';
import useContactState from '~/logic/state/contacts';
import Tiles from './components/tiles'; import Tiles from './components/tiles';
import Tile from './components/tiles/tile'; import Tile from './components/tiles/tile';
import Groups from './components/Groups'; import Groups from './components/Groups';
@ -41,7 +41,6 @@ const ScrollbarLessBox = styled(Box)`
const tutSelector = f.pick(['tutorialProgress', 'nextTutStep']); const tutSelector = f.pick(['tutorialProgress', 'nextTutStep']);
export default function LaunchApp(props) { export default function LaunchApp(props) {
const history = useHistory();
const [hashText, setHashText] = useState(props.baseHash); const [hashText, setHashText] = useState(props.baseHash);
const hashBox = ( const hashBox = (
<Box <Box
@ -133,7 +132,8 @@ export default function LaunchApp(props) {
</Col> </Col>
)} )}
}); });
const hasLoaded = useMemo(() => Object.keys(props.contacts).length > 0, [props.contacts]); const contacts = useContactState(state => state.contacts);
const hasLoaded = useMemo(() => Object.keys(contacts).length > 0, [contacts]);
useEffect(() => { useEffect(() => {
const seenTutorial = _.get(props.settings, ['tutorial', 'seen'], true); const seenTutorial = _.get(props.settings, ['tutorial', 'seen'], true);

View File

@ -28,7 +28,6 @@ export function LinkResource(props: LinkResourceProps) {
api, api,
baseUrl, baseUrl,
graphs, graphs,
contacts,
groups, groups,
associations, associations,
graphKeys, graphKeys,
@ -70,7 +69,6 @@ export function LinkResource(props: LinkResourceProps) {
<LinkWindow <LinkWindow
s3={s3} s3={s3}
association={resource} association={resource}
contacts={contacts}
resource={resourcePath} resource={resourcePath}
graph={graph} graph={graph}
unreads={unreads} unreads={unreads}
@ -103,7 +101,6 @@ export function LinkResource(props: LinkResourceProps) {
<Col width="100%" p={3} maxWidth="768px"> <Col width="100%" p={3} maxWidth="768px">
<Link to={resourceUrl}><Text px={3} bold>{'<- Back'}</Text></Link> <Link to={resourceUrl}><Text px={3} bold>{'<- Back'}</Text></Link>
<LinkItem <LinkItem
contacts={contacts}
key={node.post.index} key={node.post.index}
resource={resourcePath} resource={resourcePath}
node={node} node={node}
@ -122,7 +119,6 @@ export function LinkResource(props: LinkResourceProps) {
resource={resourcePath} resource={resourcePath}
association={association} association={association}
unreads={unreads} unreads={unreads}
contacts={contacts}
api={api} api={api}
editCommentId={editCommentId} editCommentId={editCommentId}
history={props.history} history={props.history}

View File

@ -17,7 +17,6 @@ interface LinkItemProps {
api: GlobalApi; api: GlobalApi;
group: Group; group: Group;
path: string; path: string;
contacts: Rolodex;
unreads: Unreads; unreads: Unreads;
measure: (el: any) => void; measure: (el: any) => void;
} }
@ -29,7 +28,6 @@ export const LinkItem = (props: LinkItemProps): ReactElement => {
api, api,
group, group,
path, path,
contacts,
measure, measure,
...rest ...rest
} = props; } = props;
@ -157,7 +155,6 @@ export const LinkItem = (props: LinkItemProps): ReactElement => {
<Author <Author
showImage showImage
contacts={contacts}
ship={author} ship={author}
date={node.post['time-sent']} date={node.post['time-sent']}
group={group} group={group}

View File

@ -18,6 +18,7 @@ import { getSnippet } from '~/logic/lib/publish';
import styled from 'styled-components'; import styled from 'styled-components';
import { MentionText } from '~/views/components/MentionText'; import { MentionText } from '~/views/components/MentionText';
import ChatMessage from '../chat/components/ChatMessage'; import ChatMessage from '../chat/components/ChatMessage';
import useContactState from '~/logic/state/contacts';
function getGraphModuleIcon(module: string) { function getGraphModuleIcon(module: string) {
if (module === 'link') { if (module === 'link') {
@ -67,7 +68,6 @@ const GraphUrl = ({ url, title }) => (
const GraphNodeContent = ({ const GraphNodeContent = ({
group, group,
post, post,
contacts,
mod, mod,
description, description,
index, index,
@ -81,7 +81,7 @@ const GraphNodeContent = ({
return <GraphUrl title={text} url={url} />; return <GraphUrl title={text} url={url} />;
} else if (idx.length === 3) { } else if (idx.length === 3) {
return ( return (
<MentionText content={contents} contacts={contacts} group={group} /> <MentionText content={contents} group={group} />
); );
} }
return null; return null;
@ -92,7 +92,6 @@ const GraphNodeContent = ({
<MentionText <MentionText
content={contents} content={contents}
group={group} group={group}
contacts={contacts}
fontSize='14px' fontSize='14px'
lineHeight='tall' lineHeight='tall'
/> />
@ -134,7 +133,6 @@ const GraphNodeContent = ({
containerClass='items-top cf hide-child' containerClass='items-top cf hide-child'
measure={() => {}} measure={() => {}}
group={group} group={group}
contacts={contacts}
groups={{}} groups={{}}
associations={{ graph: {}, groups: {} }} associations={{ graph: {}, groups: {} }}
msg={post} msg={post}
@ -174,7 +172,6 @@ function getNodeUrl(
} }
const GraphNode = ({ const GraphNode = ({
post, post,
contacts,
author, author,
mod, mod,
description, description,
@ -189,6 +186,7 @@ const GraphNode = ({
}) => { }) => {
author = deSig(author); author = deSig(author);
const history = useHistory(); const history = useHistory();
const contacts = useContactState(state => state.contacts);
const nodeUrl = getNodeUrl(mod, group?.hidden, groupPath, graph, index); const nodeUrl = getNodeUrl(mod, group?.hidden, groupPath, graph, index);
@ -207,7 +205,6 @@ const GraphNode = ({
{showContact && ( {showContact && (
<Author <Author
showImage showImage
contacts={contacts}
ship={author} ship={author}
date={time} date={time}
group={group} group={group}
@ -215,7 +212,6 @@ const GraphNode = ({
)} )}
<Row width='100%' p='1' flexDirection='column'> <Row width='100%' p='1' flexDirection='column'>
<GraphNodeContent <GraphNodeContent
contacts={contacts}
post={post} post={post}
mod={mod} mod={mod}
description={description} description={description}
@ -238,7 +234,6 @@ export function GraphNotification(props: {
timebox: BigInteger; timebox: BigInteger;
associations: Associations; associations: Associations;
groups: Groups; groups: Groups;
contacts: Rolodex;
api: GlobalApi; api: GlobalApi;
}) { }) {
const { contents, index, read, time, api, timebox, groups } = props; const { contents, index, read, time, api, timebox, groups } = props;
@ -266,7 +261,6 @@ export function GraphNotification(props: {
authors={authors} authors={authors}
moduleIcon={icon} moduleIcon={icon}
channel={graph} channel={graph}
contacts={props.contacts}
group={group} group={group}
description={desc} description={desc}
associations={props.associations} associations={props.associations}
@ -276,7 +270,6 @@ export function GraphNotification(props: {
<GraphNode <GraphNode
post={content} post={content}
author={content.author} author={content.author}
contacts={props.contacts}
mod={index.module} mod={index.module}
time={content?.['time-sent']} time={content?.['time-sent']}
description={index.description} description={index.description}

View File

@ -69,7 +69,6 @@ export function GroupNotification(props: GroupNotificationProps): ReactElement {
time={time} time={time}
read={read} read={read}
group={group} group={group}
contacts={props.contacts}
authors={authors} authors={authors}
description={desc} description={desc}
associations={associations} associations={associations}

View File

@ -9,13 +9,15 @@ import { Associations, Contact, Contacts, Rolodex } from '@urbit/api';
import { PropFunc } from '~/types/util'; import { PropFunc } from '~/types/util';
import { useShowNickname } from '~/logic/lib/util'; import { useShowNickname } from '~/logic/lib/util';
import Timestamp from '~/views/components/Timestamp'; import Timestamp from '~/views/components/Timestamp';
import useContactState from '~/logic/state/contacts';
const Text = (props: PropFunc<typeof Text>) => ( const Text = (props: PropFunc<typeof Text>) => (
<NormalText fontWeight="500" {...props} /> <NormalText fontWeight="500" {...props} />
); );
function Author(props: { patp: string; contacts: Contacts; last?: boolean }): ReactElement { function Author(props: { patp: string; last?: boolean }): ReactElement {
const contact: Contact | undefined = props.contacts?.[`~${props.patp}`]; const contacts = useContactState(state => state.contacts);
const contact: Contact | undefined = contacts?.[`~${props.patp}`];
const showNickname = useShowNickname(contact); const showNickname = useShowNickname(contact);
const name = contact?.nickname || `~${props.patp}`; const name = contact?.nickname || `~${props.patp}`;
@ -33,14 +35,13 @@ export function Header(props: {
archived?: boolean; archived?: boolean;
channel?: string; channel?: string;
group: string; group: string;
contacts: Rolodex;
description: string; description: string;
moduleIcon?: string; moduleIcon?: string;
time: number; time: number;
read: boolean; read: boolean;
associations: Associations; associations: Associations;
} & PropFunc<typeof Row> ): ReactElement { } & PropFunc<typeof Row> ): ReactElement {
const { description, channel, contacts, moduleIcon, read } = props; const { description, channel, moduleIcon, read } = props;
const authors = _.uniq(props.authors); const authors = _.uniq(props.authors);
@ -50,7 +51,7 @@ export function Header(props: {
f.map(([idx, p]: [string, string]) => { f.map(([idx, p]: [string, string]) => {
const lent = Math.min(3, authors.length); const lent = Math.min(3, authors.length);
const last = lent - 1 === parseInt(idx, 10); const last = lent - 1 === parseInt(idx, 10);
return <Author key={idx} contacts={contacts} patp={p} last={last} />; return <Author key={idx} patp={p} last={last} />;
}), }),
auths => ( auths => (
<React.Fragment> <React.Fragment>

View File

@ -50,7 +50,6 @@ export default function Inbox(props: {
showArchive?: boolean; showArchive?: boolean;
api: GlobalApi; api: GlobalApi;
associations: Associations; associations: Associations;
contacts: Rolodex;
filter: string[]; filter: string[];
invites: InviteType; invites: InviteType;
pendingJoin: JoinRequests; pendingJoin: JoinRequests;
@ -127,7 +126,6 @@ export default function Inbox(props: {
key={day} key={day}
label={day === 'latest' ? 'Today' : moment(day).calendar(null, calendar)} label={day === 'latest' ? 'Today' : moment(day).calendar(null, calendar)}
timeboxes={timeboxes} timeboxes={timeboxes}
contacts={props.contacts}
archive={Boolean(props.showArchive)} archive={Boolean(props.showArchive)}
associations={props.associations} associations={props.associations}
api={api} api={api}
@ -165,7 +163,6 @@ function sortIndexedNotification(
function DaySection({ function DaySection({
label, label,
contacts,
groups, groups,
archive, archive,
timeboxes, timeboxes,
@ -201,7 +198,6 @@ function DaySection({
associations={associations} associations={associations}
notification={not} notification={not}
archived={archive} archived={archive}
contacts={contacts}
groups={groups} groups={groups}
time={date} time={date}
/> />

View File

@ -12,7 +12,6 @@ interface InvitesProps {
api: GlobalApi; api: GlobalApi;
invites: IInvites; invites: IInvites;
groups: Groups; groups: Groups;
contacts: Contacts;
associations: Associations; associations: Associations;
pendingJoin: JoinRequests; pendingJoin: JoinRequests;
} }
@ -54,7 +53,6 @@ export function Invites(props: InvitesProps): ReactElement {
return ( return (
<InviteItem <InviteItem
key={resource} key={resource}
contacts={props.contacts}
groups={props.groups} groups={props.groups}
associations={props.associations} associations={props.associations}
resource={resource} resource={resource}
@ -74,7 +72,6 @@ export function Invites(props: InvitesProps): ReactElement {
uid={uid} uid={uid}
pendingJoin={pendingJoin} pendingJoin={pendingJoin}
resource={resource} resource={resource}
contacts={props.contacts}
groups={props.groups} groups={props.groups}
associations={props.associations} associations={props.associations}
/> />

View File

@ -26,7 +26,6 @@ interface NotificationProps {
api: GlobalApi; api: GlobalApi;
archived: boolean; archived: boolean;
groups: Groups; groups: Groups;
contacts: Contacts;
graphConfig: NotificationGraphConfig; graphConfig: NotificationGraphConfig;
groupConfig: GroupNotificationsConfig; groupConfig: GroupNotificationsConfig;
} }
@ -136,7 +135,6 @@ export function Notification(props: NotificationProps) {
api={props.api} api={props.api}
index={index} index={index}
contents={c} contents={c}
contacts={props.contacts}
groups={props.groups} groups={props.groups}
read={read} read={read}
archived={archived} archived={archived}
@ -156,7 +154,6 @@ export function Notification(props: NotificationProps) {
api={props.api} api={props.api}
index={index} index={index}
contents={c} contents={c}
contacts={props.contacts}
groups={props.groups} groups={props.groups}
read={read} read={read}
timebox={props.time} timebox={props.time}

View File

@ -5,8 +5,10 @@ import Helmet from 'react-helmet';
import { Box } from '@tlon/indigo-react'; import { Box } from '@tlon/indigo-react';
import { Profile } from './components/Profile'; import { Profile } from './components/Profile';
import useContactState from '~/logic/state/contacts';
export default function ProfileScreen(props: any) { export default function ProfileScreen(props: any) {
const contacts = useContactState(state => state.contacts);
return ( return (
<> <>
<Helmet defer={false}> <Helmet defer={false}>
@ -18,7 +20,7 @@ export default function ProfileScreen(props: any) {
const ship = match.params.ship; const ship = match.params.ship;
const isEdit = match.url.includes('edit'); const isEdit = match.url.includes('edit');
const isPublic = props.isContactPublic; const isPublic = props.isContactPublic;
const contact = props.contacts?.[ship]; const contact = contacts?.[ship];
return ( return (
<Box height="100%" px={[0, 3]} pb={[0, 3]} borderRadius={1}> <Box height="100%" px={[0, 3]} pb={[0, 3]} borderRadius={1}>
@ -35,7 +37,7 @@ export default function ProfileScreen(props: any) {
<Box> <Box>
<Profile <Profile
ship={ship} ship={ship}
hasLoaded={Object.keys(props.contacts).length !== 0} hasLoaded={Object.keys(contacts).length !== 0}
associations={props.associations} associations={props.associations}
groups={props.groups} groups={props.groups}
contact={contact} contact={contact}

View File

@ -24,7 +24,6 @@ export function PublishResource(props: PublishResourceProps) {
api={api} api={api}
ship={ship} ship={ship}
book={book} book={book}
contacts={props.contacts}
groups={props.groups} groups={props.groups}
associations={props.associations} associations={props.associations}
association={association} association={association}

View File

@ -22,7 +22,6 @@ interface MetadataFormProps {
host: string; host: string;
book: string; book: string;
association: Association; association: Association;
contacts: Contacts;
api: GlobalApi; api: GlobalApi;
} }

View File

@ -19,7 +19,6 @@ interface NoteProps {
unreads: Unreads; unreads: Unreads;
association: Association; association: Association;
notebook: Graph; notebook: Graph;
contacts: Contacts;
api: GlobalApi; api: GlobalApi;
rootUrl: string; rootUrl: string;
baseUrl: string; baseUrl: string;
@ -29,7 +28,7 @@ interface NoteProps {
export function Note(props: NoteProps & RouteComponentProps) { export function Note(props: NoteProps & RouteComponentProps) {
const [deleting, setDeleting] = useState(false); const [deleting, setDeleting] = useState(false);
const { notebook, note, contacts, ship, book, api, rootUrl, baseUrl, group } = props; const { notebook, note, ship, book, api, rootUrl, baseUrl, group } = props;
const editCommentId = props.match.params.commentId; const editCommentId = props.match.params.commentId;
const deletePost = async () => { const deletePost = async () => {
@ -100,7 +99,6 @@ export function Note(props: NoteProps & RouteComponentProps) {
<Box display="flex"> <Box display="flex">
<Author <Author
ship={post?.author} ship={post?.author}
contacts={contacts}
date={post?.['time-sent']} date={post?.['time-sent']}
/> />
<Text ml={2}>{adminLinks}</Text> <Text ml={2}>{adminLinks}</Text>
@ -120,7 +118,6 @@ export function Note(props: NoteProps & RouteComponentProps) {
name={props.book} name={props.book}
unreads={props.unreads} unreads={props.unreads}
comments={comments} comments={comments}
contacts={props.contacts}
association={props.association} association={props.association}
api={props.api} api={props.api}
baseUrl={baseUrl} baseUrl={baseUrl}

View File

@ -21,7 +21,6 @@ interface NotePreviewProps {
node: GraphNode; node: GraphNode;
baseUrl: string; baseUrl: string;
unreads: Unreads; unreads: Unreads;
contacts: Contacts;
api: GlobalApi; api: GlobalApi;
group: Group; group: Group;
} }
@ -31,7 +30,7 @@ const WrappedBox = styled(Box)`
`; `;
export function NotePreview(props: NotePreviewProps) { export function NotePreview(props: NotePreviewProps) {
const { node, contacts, group } = props; const { node, group } = props;
const { post } = node; const { post } = node;
if (!post) { if (!post) {
return null; return null;
@ -79,7 +78,6 @@ export function NotePreview(props: NotePreviewProps) {
<Row minWidth='0' flexShrink={0} width="100%" justifyContent="space-between" py={3} bg="white"> <Row minWidth='0' flexShrink={0} width="100%" justifyContent="space-between" py={3} bg="white">
<Author <Author
showImage showImage
contacts={contacts}
ship={post?.author} ship={post?.author}
date={post?.['time-sent']} date={post?.['time-sent']}
group={group} group={group}

View File

@ -14,7 +14,6 @@ interface NoteRoutesProps {
note: GraphNode; note: GraphNode;
noteId: number; noteId: number;
notebook: Graph; notebook: Graph;
contacts: Contacts;
api: GlobalApi; api: GlobalApi;
association: Association; association: Association;
baseUrl?: string; baseUrl?: string;

View File

@ -7,6 +7,7 @@ import { Contacts, Rolodex, Groups, Associations, Graph, Association, Unreads }
import { NotebookPosts } from './NotebookPosts'; import { NotebookPosts } from './NotebookPosts';
import GlobalApi from '~/logic/api/global'; import GlobalApi from '~/logic/api/global';
import { useShowNickname } from '~/logic/lib/util'; import { useShowNickname } from '~/logic/lib/util';
import useContactState from '~/logic/state/contacts';
interface NotebookProps { interface NotebookProps {
api: GlobalApi; api: GlobalApi;
@ -15,7 +16,6 @@ interface NotebookProps {
graph: Graph; graph: Graph;
association: Association; association: Association;
associations: Associations; associations: Associations;
contacts: Rolodex;
groups: Groups; groups: Groups;
baseUrl: string; baseUrl: string;
rootUrl: string; rootUrl: string;
@ -26,7 +26,6 @@ export function Notebook(props: NotebookProps & RouteComponentProps): ReactEleme
const { const {
ship, ship,
book, book,
contacts,
groups, groups,
association, association,
graph graph
@ -38,6 +37,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps): ReactEleme
} }
const relativePath = (p: string) => props.baseUrl + p; const relativePath = (p: string) => props.baseUrl + p;
const contacts = useContactState(state => state.contacts);
const contact = contacts?.[`~${ship}`]; const contact = contacts?.[`~${ship}`];
console.log(association.resource); console.log(association.resource);
@ -60,7 +60,6 @@ export function Notebook(props: NotebookProps & RouteComponentProps): ReactEleme
graph={graph} graph={graph}
host={ship} host={ship}
book={book} book={book}
contacts={contacts}
unreads={props.unreads} unreads={props.unreads}
baseUrl={props.baseUrl} baseUrl={props.baseUrl}
api={props.api} api={props.api}

View File

@ -2,9 +2,9 @@ import React, { Component } from 'react';
import { Col } from '@tlon/indigo-react'; import { Col } from '@tlon/indigo-react';
import { NotePreview } from './NotePreview'; import { NotePreview } from './NotePreview';
import { Contacts, Graph, Unreads, Group } from '@urbit/api'; import { Contacts, Graph, Unreads, Group } from '@urbit/api';
import useContactState from '~/logic/state/contacts';
interface NotebookPostsProps { interface NotebookPostsProps {
contacts: Contacts;
graph: Graph; graph: Graph;
host: string; host: string;
book: string; book: string;
@ -17,6 +17,7 @@ interface NotebookPostsProps {
} }
export function NotebookPosts(props: NotebookPostsProps) { export function NotebookPosts(props: NotebookPostsProps) {
const contacts = useContactState(state => state.contacts);
return ( return (
<Col> <Col>
{Array.from(props.graph || []).map( {Array.from(props.graph || []).map(
@ -27,8 +28,7 @@ export function NotebookPosts(props: NotebookPostsProps) {
host={props.host} host={props.host}
book={props.book} book={props.book}
unreads={props.unreads} unreads={props.unreads}
contact={props.contacts[`~${node.post.author}`]} contact={contacts[`~${node.post.author}`]}
contacts={props.contacts}
node={node} node={node}
baseUrl={props.baseUrl} baseUrl={props.baseUrl}
api={props.api} api={props.api}

View File

@ -24,7 +24,6 @@ interface NotebookRoutesProps {
book: string; book: string;
graphs: Graphs; graphs: Graphs;
unreads: Unreads; unreads: Unreads;
contacts: Rolodex;
groups: Groups; groups: Groups;
baseUrl: string; baseUrl: string;
rootUrl: string; rootUrl: string;
@ -36,7 +35,7 @@ interface NotebookRoutesProps {
export function NotebookRoutes( export function NotebookRoutes(
props: NotebookRoutesProps & RouteComponentProps props: NotebookRoutesProps & RouteComponentProps
) { ) {
const { ship, book, api, contacts, baseUrl, rootUrl, groups } = props; const { ship, book, api, baseUrl, rootUrl, groups } = props;
useEffect(() => { useEffect(() => {
ship && book && api.graph.getGraph(ship, book); ship && book && api.graph.getGraph(ship, book);
@ -59,7 +58,6 @@ export function NotebookRoutes(
return <Notebook return <Notebook
{...props} {...props}
graph={graph} graph={graph}
contacts={contacts}
association={props.association} association={props.association}
rootUrl={rootUrl} rootUrl={rootUrl}
baseUrl={baseUrl} baseUrl={baseUrl}
@ -106,7 +104,6 @@ export function NotebookRoutes(
notebook={graph} notebook={graph}
unreads={props.unreads} unreads={props.unreads}
noteId={noteIdNum} noteId={noteIdNum}
contacts={contacts}
association={props.association} association={props.association}
group={group} group={group}
s3={props.s3} s3={props.s3}

View File

@ -7,7 +7,7 @@ import { AsyncButton } from '~/views/components/AsyncButton';
export class Writers extends Component { export class Writers extends Component {
render() { render() {
const { association, groups, contacts, api } = this.props; const { association, groups, api } = this.props;
const resource = resourceFromPath(association?.group); const resource = resourceFromPath(association?.group);
@ -40,7 +40,6 @@ export class Writers extends Component {
<Form> <Form>
<ShipSearch <ShipSearch
groups={groups} groups={groups}
contacts={contacts}
id="ships" id="ships"
label="" label=""
maxLength={undefined} maxLength={undefined}

View File

@ -11,9 +11,9 @@ import OverlaySigil from './OverlaySigil';
import { Sigil } from '~/logic/lib/sigil'; import { Sigil } from '~/logic/lib/sigil';
import GlobalApi from '~/logic/api/global'; import GlobalApi from '~/logic/api/global';
import Timestamp from './Timestamp'; import Timestamp from './Timestamp';
import useContactState from '~/logic/state/contacts';
interface AuthorProps { interface AuthorProps {
contacts: Contacts;
ship: string; ship: string;
date: number; date: number;
showImage?: boolean; showImage?: boolean;
@ -25,9 +25,10 @@ interface AuthorProps {
// eslint-disable-next-line max-lines-per-function // eslint-disable-next-line max-lines-per-function
export default function Author(props: AuthorProps): ReactElement { export default function Author(props: AuthorProps): ReactElement {
const { contacts, ship = '', date, showImage, group } = props; const { ship = '', date, showImage, group } = props;
const history = useHistory(); const history = useHistory();
let contact; let contact;
const contacts = useContactState(state => state.contacts);
if (contacts) { if (contacts) {
contact = `~${deSig(ship)}` in contacts ? contacts[`~${deSig(ship)}`] : null; contact = `~${deSig(ship)}` in contacts ? contacts[`~${deSig(ship)}`] : null;
} }

View File

@ -21,7 +21,6 @@ interface CommentItemProps {
pending?: boolean; pending?: boolean;
comment: GraphNode; comment: GraphNode;
baseUrl: string; baseUrl: string;
contacts: Contacts;
unread: boolean; unread: boolean;
name: string; name: string;
ship: string; ship: string;
@ -30,7 +29,7 @@ interface CommentItemProps {
} }
export function CommentItem(props: CommentItemProps): ReactElement { export function CommentItem(props: CommentItemProps): ReactElement {
const { ship, contacts, name, api, comment, group } = props; const { ship, name, api, comment, group } = props;
const [, post] = getLatestCommentRevision(comment); const [, post] = getLatestCommentRevision(comment);
const disabled = props.pending || window.ship !== post?.author; const disabled = props.pending || window.ship !== post?.author;
@ -47,7 +46,6 @@ export function CommentItem(props: CommentItemProps): ReactElement {
<Row bg="white" my={3}> <Row bg="white" my={3}>
<Author <Author
showImage showImage
contacts={contacts}
ship={post?.author} ship={post?.author}
date={post?.['time-sent']} date={post?.['time-sent']}
unread={props.unread} unread={props.unread}
@ -73,7 +71,6 @@ export function CommentItem(props: CommentItemProps): ReactElement {
</Row> </Row>
<Box mb={2}> <Box mb={2}>
<MentionText <MentionText
contacts={contacts}
group={group} group={group}
content={post?.contents} content={post?.contents}
/> />

View File

@ -21,7 +21,6 @@ interface CommentsProps {
ship: string; ship: string;
editCommentId: string; editCommentId: string;
baseUrl: string; baseUrl: string;
contacts: Contacts;
api: GlobalApi; api: GlobalApi;
group: Group; group: Group;
} }
@ -130,7 +129,6 @@ export function Comments(props: CommentsProps & PropFunc<typeof Col>) {
<CommentItem <CommentItem
comment={comment} comment={comment}
key={idx.toString()} key={idx.toString()}
contacts={props.contacts}
api={api} api={api}
name={name} name={name}
ship={ship} ship={ship}

View File

@ -29,7 +29,6 @@ interface InviteItemProps {
app?: string; app?: string;
uid?: string; uid?: string;
api: GlobalApi; api: GlobalApi;
contacts: Contacts;
} }
export function InviteItem(props: InviteItemProps) { export function InviteItem(props: InviteItemProps) {

View File

@ -6,18 +6,18 @@ import RichText from '~/views/components/RichText';
import { cite, useShowNickname, uxToHex } from '~/logic/lib/util'; import { cite, useShowNickname, uxToHex } from '~/logic/lib/util';
import OverlaySigil from '~/views/components/OverlaySigil'; import OverlaySigil from '~/views/components/OverlaySigil';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import useContactState from '~/logic/state/contacts';
interface MentionTextProps { interface MentionTextProps {
contact?: Contact; contact?: Contact;
contacts?: Contacts;
content: Content[]; content: Content[];
group: Group; group: Group;
} }
export function MentionText(props: MentionTextProps) { export function MentionText(props: MentionTextProps) {
const { content, contacts, contact, group, ...rest } = props; const { content, contact, group, ...rest } = props;
return ( return (
<RichText contacts={contacts} contact={contact} group={group} {...rest}> <RichText contact={contact} group={group} {...rest}>
{content.reduce((accum, c) => { {content.reduce((accum, c) => {
if ('text' in c) { if ('text' in c) {
return accum + c.text; return accum + c.text;
@ -34,14 +34,14 @@ export function MentionText(props: MentionTextProps) {
export function Mention(props: { export function Mention(props: {
contact: Contact; contact: Contact;
contacts?: Contacts;
group: Group; group: Group;
scrollWindow?: HTMLElement; scrollWindow?: HTMLElement;
ship: string; ship: string;
first?: Boolean; first?: Boolean;
}) { }) {
const { contacts, ship, scrollWindow, first, ...rest } = props; const { ship, scrollWindow, first, ...rest } = props;
let { contact } = props; let { contact } = props;
const contacts = useContactState(state => state.contacts);
contact = contact?.color ? contact : contacts?.[ship]; contact = contact?.color ? contact : contacts?.[ship];
const history = useHistory(); const history = useHistory();
const showNickname = useShowNickname(contact); const showNickname = useShowNickname(contact);

View File

@ -42,7 +42,7 @@ const RichText = React.memo(({ disableRemoteContent, ...props }) => (
linkReference: (linkProps) => { linkReference: (linkProps) => {
const linkText = String(linkProps.children[0].props.children); const linkText = String(linkProps.children[0].props.children);
if (isValidPatp(linkText)) { if (isValidPatp(linkText)) {
return <Mention contacts={props.contacts || {}} contact={props.contact || {}} group={props.group} ship={deSig(linkText)} />; return <Mention contact={props.contact || {}} group={props.group} ship={deSig(linkText)} />;
} }
return linkText; return linkText;
}, },

View File

@ -24,6 +24,7 @@ import { Rolodex, Groups } from '@urbit/api';
import { DropdownSearch } from './DropdownSearch'; import { DropdownSearch } from './DropdownSearch';
import { cite, deSig } from '~/logic/lib/util'; import { cite, deSig } from '~/logic/lib/util';
import { HoverBox } from './HoverBox'; import { HoverBox } from './HoverBox';
import useContactState from '~/logic/state/contacts';
interface InviteSearchProps<I extends string> { interface InviteSearchProps<I extends string> {
autoFocus?: boolean; autoFocus?: boolean;
@ -121,9 +122,11 @@ export function ShipSearch<I extends string, V extends Value<I>>(
const pills = selected.slice(0, inputIdx.current); const pills = selected.slice(0, inputIdx.current);
const contacts = useContactState(state => state.contacts);
const [peers, nicknames] = useMemo( const [peers, nicknames] = useMemo(
() => getNicknameForShips(props.groups, props.contacts), () => getNicknameForShips(props.groups, contacts),
[props.contacts, props.groups] [contacts, props.groups]
); );
const renderCandidate = useCallback( const renderCandidate = useCallback(

View File

@ -16,10 +16,10 @@ import defaultApps from '~/logic/lib/default-apps';
import { useOutsideClick } from '~/logic/lib/useOutsideClick'; import { useOutsideClick } from '~/logic/lib/useOutsideClick';
import { Portal } from '../Portal'; import { Portal } from '../Portal';
import { Tile } from '~/types'; import { Tile } from '~/types';
import useContactState from '~/logic/state/contacts';
interface OmniboxProps { interface OmniboxProps {
associations: Associations; associations: Associations;
contacts: Contacts;
groups: Groups; groups: Groups;
tiles: { tiles: {
[app: string]: Tile; [app: string]: Tile;
@ -40,13 +40,14 @@ export function Omnibox(props: OmniboxProps) {
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const [selected, setSelected] = useState<[] | [string, string]>([]); const [selected, setSelected] = useState<[] | [string, string]>([]);
const contactState = useContactState(state => state.contacts);
const contacts = useMemo(() => { const contacts = useMemo(() => {
const maybeShip = `~${deSig(query)}`; const maybeShip = `~${deSig(query)}`;
return ob.isValidPatp(maybeShip) return ob.isValidPatp(maybeShip)
? { ...props.contacts, [maybeShip]: {} } ? { ...contactState, [maybeShip]: {} }
: props.contacts; : contactState;
}, [props.contacts, query]); }, [contactState, query]);
const index = useMemo(() => { const index = useMemo(() => {
const selectedGroup = location.pathname.startsWith('/~landscape/ship/') const selectedGroup = location.pathname.startsWith('/~landscape/ship/')
@ -257,7 +258,6 @@ export function Omnibox(props: OmniboxProps) {
selected={sel} selected={sel}
invites={props.invites} invites={props.invites}
notifications={props.notifications} notifications={props.notifications}
contacts={props.contacts}
/> />
))} ))}
</Box> </Box>
@ -265,7 +265,7 @@ export function Omnibox(props: OmniboxProps) {
}) })
} }
</Box>; </Box>;
}, [results, navigate, selected, props.contacts, props.notifications, props.invites]); }, [results, navigate, selected, contactState, props.notifications, props.invites]);
return ( return (
<Portal> <Portal>

View File

@ -177,7 +177,7 @@ export function GraphPermissions(props: GraphPermissionsProps) {
vip={association.metadata.vip} vip={association.metadata.vip}
/> />
</Col> </Col>
<ChannelWritePerms contacts={props.contacts} groups={props.groups} /> <ChannelWritePerms groups={props.groups} />
{association.metadata.module !== 'chat' && ( {association.metadata.module !== 'chat' && (
<Checkbox <Checkbox
id="readerComments" id="readerComments"

View File

@ -34,7 +34,6 @@ export function ChannelWritePerms<
{values.writePerms === 'subset' && ( {values.writePerms === 'subset' && (
<ShipSearch <ShipSearch
groups={props.groups} groups={props.groups}
contacts={props.contacts}
id="writers" id="writers"
label="" label=""
maxLength={undefined} maxLength={undefined}

View File

@ -27,6 +27,7 @@ import '~/views/apps/publish/css/custom.css';
import { getGroupFromWorkspace } from '~/logic/lib/workspace'; import { getGroupFromWorkspace } from '~/logic/lib/workspace';
import { GroupSummary } from './GroupSummary'; import { GroupSummary } from './GroupSummary';
import { Workspace } from '~/types/workspace'; import { Workspace } from '~/types/workspace';
import useContactState from '~/logic/state/contacts';
type GroupsPaneProps = StoreState & { type GroupsPaneProps = StoreState & {
baseUrl: string; baseUrl: string;
@ -35,7 +36,8 @@ type GroupsPaneProps = StoreState & {
}; };
export function GroupsPane(props: GroupsPaneProps) { export function GroupsPane(props: GroupsPaneProps) {
const { baseUrl, associations, groups, contacts, api, workspace } = props; const { baseUrl, associations, groups, api, workspace } = props;
const contacts = useContactState(state => state.contacts);
const relativePath = (path: string) => baseUrl + path; const relativePath = (path: string) => baseUrl + path;
const groupPath = getGroupFromWorkspace(workspace); const groupPath = getGroupFromWorkspace(workspace);
@ -82,7 +84,6 @@ export function GroupsPane(props: GroupsPaneProps) {
association={groupAssociation!} association={groupAssociation!}
baseUrl={baseUrl} baseUrl={baseUrl}
groups={props.groups} groups={props.groups}
contacts={props.contacts}
workspace={workspace} workspace={workspace}
/> />
</> </>
@ -181,7 +182,6 @@ export function GroupsPane(props: GroupsPaneProps) {
associations={associations} associations={associations}
groups={groups} groups={groups}
group={groupPath} group={groupPath}
contacts={props.contacts}
workspace={workspace} workspace={workspace}
/> />
{popovers(routeProps, baseUrl)} {popovers(routeProps, baseUrl)}

View File

@ -115,7 +115,6 @@ export function InvitePopover(props: InvitePopoverProps) {
</Box> </Box>
<ShipSearch <ShipSearch
groups={props.groups} groups={props.groups}
contacts={props.contacts}
id="ships" id="ships"
label="" label=""
autoFocus autoFocus

View File

@ -178,13 +178,11 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps): ReactE
{(workspace?.type === 'home' || workspace?.type === 'messages') ? ( {(workspace?.type === 'home' || workspace?.type === 'messages') ? (
<ShipSearch <ShipSearch
groups={props.groups} groups={props.groups}
contacts={props.contacts}
id="ships" id="ships"
label="Invitees" label="Invitees"
/>) : ( />) : (
<ChannelWritePerms <ChannelWritePerms
groups={props.groups} groups={props.groups}
contacts={props.contacts}
/> />
)} )}
<Box justifySelf="start"> <Box justifySelf="start">

View File

@ -30,6 +30,7 @@ import { Dropdown } from '~/views/components/Dropdown';
import GlobalApi from '~/logic/api/global'; import GlobalApi from '~/logic/api/global';
import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction'; import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction';
import useLocalState from '~/logic/state/local'; import useLocalState from '~/logic/state/local';
import useContactState from '~/logic/state/contacts';
const TruncText = styled(Text)` const TruncText = styled(Text)`
white-space: nowrap; white-space: nowrap;
@ -103,7 +104,6 @@ const Tab = ({ selected, id, label, setSelected }) => (
); );
export function Participants(props: { export function Participants(props: {
contacts: Contacts;
group: Group; group: Group;
association: Association; association: Association;
api: GlobalApi; api: GlobalApi;
@ -136,9 +136,10 @@ export function Participants(props: {
const adminCount = props.group.tags?.role?.admin?.size || 0; const adminCount = props.group.tags?.role?.admin?.size || 0;
const isInvite = 'invite' in props.group.policy; const isInvite = 'invite' in props.group.policy;
const contacts = useContactState(state => state.contacts);
const [participants, pendingCount, memberCount] = getParticipants( const [participants, pendingCount, memberCount] = getParticipants(
props.contacts, contacts,
props.group props.group
); );

View File

@ -20,7 +20,6 @@ import { S3State } from '~/types';
export function PopoverRoutes( export function PopoverRoutes(
props: { props: {
baseUrl: string; baseUrl: string;
contacts: Contacts;
group: Group; group: Group;
association: Association; association: Association;
associations: Associations; associations: Associations;
@ -134,7 +133,6 @@ export function PopoverRoutes(
{view === 'participants' && ( {view === 'participants' && (
<Participants <Participants
group={props.group} group={props.group}
contacts={props.contacts}
association={props.association} association={props.association}
api={props.api} api={props.api}
/> />

View File

@ -58,7 +58,6 @@ export function Resource(props: ResourceProps): ReactElement {
association={association} association={association}
group={props.groups?.[selectedGroup]} group={props.groups?.[selectedGroup]}
groups={props.groups} groups={props.groups}
contacts={props.contacts}
api={props.api} api={props.api}
baseUrl={relativePath('')} baseUrl={relativePath('')}
rootUrl={props.baseUrl} rootUrl={props.baseUrl}

View File

@ -11,6 +11,7 @@ import RichText from '~/views/components/RichText';
import GlobalApi from '~/logic/api/global'; import GlobalApi from '~/logic/api/global';
import { isWriter } from '~/logic/lib/group'; import { isWriter } from '~/logic/lib/group';
import { getItemTitle } from '~/logic/lib/util'; import { getItemTitle } from '~/logic/lib/util';
import useContactState from '~/logic/state/contacts';
const TruncatedBox = styled(Box)` const TruncatedBox = styled(Box)`
white-space: pre; white-space: pre;
@ -48,9 +49,11 @@ export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement {
let recipient = false; let recipient = false;
const contacts = useContactState(state => state.contacts);
if (urbitOb.isValidPatp(title)) { if (urbitOb.isValidPatp(title)) {
recipient = title; recipient = title;
title = (props.contacts?.[title]?.nickname) ? props.contacts[title].nickname : title; title = (contacts?.[title]?.nickname) ? contacts[title].nickname : title;
} }
const [, , ship, resource] = rid.split('/'); const [, , ship, resource] = rid.split('/');

View File

@ -91,7 +91,6 @@ export function Sidebar(props: SidebarProps): ReactElement {
/> />
<SidebarListHeader <SidebarListHeader
associations={associations} associations={associations}
contacts={props.contacts}
baseUrl={props.baseUrl} baseUrl={props.baseUrl}
groups={props.groups} groups={props.groups}
initialValues={config} initialValues={config}
@ -110,7 +109,6 @@ export function Sidebar(props: SidebarProps): ReactElement {
apps={props.apps} apps={props.apps}
baseUrl={props.baseUrl} baseUrl={props.baseUrl}
workspace={workspace} workspace={workspace}
contacts={props.contacts}
/> />
</ScrollbarLessCol> </ScrollbarLessCol>
); );

View File

@ -12,6 +12,7 @@ import useLocalState from '~/logic/state/local';
import { TUTORIAL_HOST, TUTORIAL_GROUP } from '~/logic/lib/tutorialModal'; import { TUTORIAL_HOST, TUTORIAL_GROUP } from '~/logic/lib/tutorialModal';
import { SidebarAppConfigs, SidebarItemStatus } from './types'; import { SidebarAppConfigs, SidebarItemStatus } from './types';
import { Workspace } from '~/types/workspace'; import { Workspace } from '~/types/workspace';
import useContactState from '~/logic/state/contacts';
function SidebarItemIndicator(props: { status?: SidebarItemStatus }) { function SidebarItemIndicator(props: { status?: SidebarItemStatus }) {
switch (props.status) { switch (props.status) {
@ -84,14 +85,16 @@ export function SidebarItem(props: {
let img = null; let img = null;
const contacts = useContactState(state => state.contacts);
if (urbitOb.isValidPatp(title)) { if (urbitOb.isValidPatp(title)) {
if (props.contacts?.[title]?.avatar && !hideAvatars) { if (contacts?.[title]?.avatar && !hideAvatars) {
img = <BaseImage src={props.contacts[title].avatar} width='16px' height='16px' borderRadius={2} />; img = <BaseImage src={contacts[title].avatar} width='16px' height='16px' borderRadius={2} />;
} else { } else {
img = <Sigil ship={title} color={`#${uxToHex(props.contacts?.[title]?.color || '0x0')}`} icon padding={2} size={16} />; img = <Sigil ship={title} color={`#${uxToHex(contacts?.[title]?.color || '0x0')}`} icon padding={2} size={16} />;
} }
if (props.contacts?.[title]?.nickname && !hideNicknames) { if (contacts?.[title]?.nickname && !hideNicknames) {
title = props.contacts[title].nickname; title = contacts[title].nickname;
} }
} else { } else {
img = <Box flexShrink={0} height={16} width={16} borderRadius={2} backgroundColor={`#${uxToHex(props?.association?.metadata?.color)}` || '#000000'} />; img = <Box flexShrink={0} height={16} width={16} borderRadius={2} backgroundColor={`#${uxToHex(props?.association?.metadata?.color)}` || '#000000'} />;

View File

@ -77,7 +77,6 @@ export function SidebarList(props: {
apps={props.apps} apps={props.apps}
hideUnjoined={config.hideUnjoined} hideUnjoined={config.hideUnjoined}
groups={props.groups} groups={props.groups}
contacts={props.contacts}
workspace={workspace} workspace={workspace}
/> />
); );

View File

@ -87,7 +87,6 @@ export function SidebarListHeader(props: {
api={props.api} api={props.api}
history={props.history} history={props.history}
associations={props.associations} associations={props.associations}
contacts={props.contacts}
groups={props.groups} groups={props.groups}
workspace={props.workspace} workspace={props.workspace}
/> />

View File

@ -49,7 +49,6 @@ export function Skeleton(props: SkeletonProps): ReactElement {
gridTemplateRows="100%" gridTemplateRows="100%"
> >
<Sidebar <Sidebar
contacts={props.contacts}
api={props.api} api={props.api}
recentGroups={props.recentGroups} recentGroups={props.recentGroups}
selected={props.selected} selected={props.selected}

View File

@ -117,7 +117,6 @@ export default class Landscape extends Component<LandscapeProps, Record<string,
<NewGroup <NewGroup
associations={props.associations} associations={props.associations}
groups={props.groups} groups={props.groups}
contacts={props.contacts}
api={props.api} api={props.api}
{...routeProps} {...routeProps}
/> />
@ -141,7 +140,6 @@ export default class Landscape extends Component<LandscapeProps, Record<string,
<Box maxWidth="300px"> <Box maxWidth="300px">
<JoinGroup <JoinGroup
groups={props.groups} groups={props.groups}
contacts={props.contacts}
api={props.api} api={props.api}
autojoin={autojoin} autojoin={autojoin}
{...routeProps} {...routeProps}