chat-fe: pretty group joining link

This commit is contained in:
Liam Fitzgerald 2021-02-10 16:27:49 +10:00
parent afb0424efd
commit 303dc6b3bd
No known key found for this signature in database
GPG Key ID: D390E12C61D1CFFB
7 changed files with 150 additions and 36 deletions

View File

@ -11,6 +11,7 @@ import React, {
import { Box } from "@tlon/indigo-react"; import { Box } from "@tlon/indigo-react";
import { useOutsideClick } from "./useOutsideClick"; import { useOutsideClick } from "./useOutsideClick";
import { ModalOverlay } from "~/views/components/ModalOverlay"; import { ModalOverlay } from "~/views/components/ModalOverlay";
import {Portal} from "~/views/components/Portal";
type ModalFunc = (dismiss: () => void) => JSX.Element; type ModalFunc = (dismiss: () => void) => JSX.Element;
interface UseModalProps { interface UseModalProps {
@ -49,6 +50,7 @@ export function useModal(props: UseModalProps): UseModalResult {
const modal = useMemo( const modal = useMemo(
() => () =>
!inner ? null : ( !inner ? null : (
<Portal>
<ModalOverlay <ModalOverlay
ref={innerRef} ref={innerRef}
maxWidth="500px" maxWidth="500px"
@ -65,6 +67,7 @@ export function useModal(props: UseModalProps): UseModalResult {
> >
{inner} {inner}
</ModalOverlay> </ModalOverlay>
</Portal>
), ),
[inner, dismiss] [inner, dismiss]
); );

View File

@ -111,6 +111,8 @@ export function ChatResource(props: ChatResourceProps) {
envelopes={[]} envelopes={[]}
contacts={contacts} contacts={contacts}
association={props.association} association={props.association}
associations={props.associations}
groups={props.groups}
group={group} group={group}
ship={owner} ship={owner}
station={station} station={station}

View File

@ -17,7 +17,7 @@ import {
useShowNickname, useShowNickname,
useHovering useHovering
} from '~/logic/lib/util'; } from '~/logic/lib/util';
import { Group, Association, Contacts, Post } from '~/types'; import { Group, Association, Contacts, Post, Groups, Associations } from '~/types';
import TextContent from './content/text'; import TextContent from './content/text';
import CodeContent from './content/code'; import CodeContent from './content/code';
import RemoteContent from '~/views/components/RemoteContent'; import RemoteContent from '~/views/components/RemoteContent';
@ -108,7 +108,9 @@ export default class ChatMessage extends Component<ChatMessageProps> {
history, history,
api, api,
highlighted, highlighted,
fontSize fontSize,
groups,
associations
} = this.props; } = this.props;
const renderSigil = Boolean( const renderSigil = Boolean(
@ -145,7 +147,9 @@ export default class ChatMessage extends Component<ChatMessageProps> {
api, api,
scrollWindow, scrollWindow,
highlighted, highlighted,
fontSize fontSize,
associations,
groups,
}; };
const unreadContainerStyle = { const unreadContainerStyle = {
@ -206,6 +210,8 @@ interface MessageProps {
style: any; style: any;
measure(element): void; measure(element): void;
scrollWindow: HTMLDivElement; scrollWindow: HTMLDivElement;
associations: Associations;
groups: Groups;
} }
export const MessageWithSigil = (props) => { export const MessageWithSigil = (props) => {
@ -214,6 +220,8 @@ export const MessageWithSigil = (props) => {
timestamp, timestamp,
contacts, contacts,
association, association,
associations,
groups,
group, group,
measure, measure,
api, api,
@ -355,6 +363,9 @@ export const MessageWithSigil = (props) => {
scrollWindow={scrollWindow} scrollWindow={scrollWindow}
fontSize={fontSize} fontSize={fontSize}
group={group} group={group}
api={api}
associations={associations}
groups={groups}
/> />
))} ))}
</ContentBox> </ContentBox>
@ -375,6 +386,9 @@ export const MessageWithoutSigil = ({
msg, msg,
measure, measure,
group, group,
api,
associations,
groups,
scrollWindow scrollWindow
}) => { }) => {
const { hovering, bind } = useHovering(); const { hovering, bind } = useHovering();
@ -409,6 +423,9 @@ export const MessageWithoutSigil = ({
group={group} group={group}
measure={measure} measure={measure}
scrollWindow={scrollWindow} scrollWindow={scrollWindow}
groups={groups}
associations={associations}
api={api}
/> />
))} ))}
</ContentBox> </ContentBox>
@ -419,6 +436,9 @@ export const MessageWithoutSigil = ({
export const MessageContent = ({ export const MessageContent = ({
content, content,
contacts, contacts,
api,
associations,
groups,
measure, measure,
scrollWindow, scrollWindow,
fontSize, fontSize,
@ -461,7 +481,15 @@ export const MessageContent = ({
</Box> </Box>
); );
} else if ('text' in content) { } else if ('text' in content) {
return <TextContent fontSize={fontSize} content={content} />; return (
<TextContent
associations={associations}
groups={groups}
measure={measure}
api={api}
fontSize={fontSize}
content={content}
/>);
} else if ('mention' in content) { } else if ('mention' in content) {
return ( return (
<Mention <Mention

View File

@ -6,8 +6,8 @@ import bigInt, { BigInteger } from 'big-integer';
import GlobalApi from "~/logic/api/global"; import GlobalApi from "~/logic/api/global";
import { Patp, Path } from "~/types/noun"; import { Patp, Path } from "~/types/noun";
import { Contacts } from "~/types/contact-update"; import { Contacts } from "~/types/contact-update";
import { Association } from "~/types/metadata-update"; import { Association, Associations } from "~/types/metadata-update";
import { Group } from "~/types/group-update"; import { Group, Groups } from "~/types/group-update";
import { Envelope, IMessage } from "~/types/chat-update"; import { Envelope, IMessage } from "~/types/chat-update";
import { Graph } from "~/types"; import { Graph } from "~/types";
@ -39,6 +39,8 @@ type ChatWindowProps = RouteComponentProps<{
station: any; station: any;
api: GlobalApi; api: GlobalApi;
scrollTo?: number; scrollTo?: number;
associations: Associations;
groups: Groups;
} }
interface ChatWindowState { interface ChatWindowState {
@ -247,13 +249,15 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
contacts, contacts,
mailboxSize, mailboxSize,
graph, graph,
history history,
groups,
associations
} = this.props; } = this.props;
const unreadMarkerRef = this.unreadMarkerRef; const unreadMarkerRef = this.unreadMarkerRef;
const messageProps = { association, group, contacts, unreadMarkerRef, history, api }; const messageProps = { association, group, contacts, unreadMarkerRef, history, api, groups, associations };
const keys = graph.keys().reverse(); const keys = graph.keys().reverse();
const unreadIndex = graph.keys()[this.props.unreadCount]; const unreadIndex = graph.keys()[this.props.unreadCount];

View File

@ -1,10 +1,11 @@
import React, { Component } from 'react'; import React, { Component, useState, useEffect } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import RemarkDisableTokenizers from 'remark-disable-tokenizers'; import RemarkDisableTokenizers from 'remark-disable-tokenizers';
import RemarkBreaks from 'remark-breaks'; import RemarkBreaks from 'remark-breaks';
import urbitOb from 'urbit-ob'; import urbitOb from 'urbit-ob';
import { Text } from '@tlon/indigo-react'; import { Text } from '@tlon/indigo-react';
import { GroupLink } from "~/views/components/GroupLink";
const DISABLED_BLOCK_TOKENS = [ const DISABLED_BLOCK_TOKENS = [
'indentedCode', 'indentedCode',
@ -73,28 +74,29 @@ const MessageMarkdown = React.memo(props => (
plugins={[RemarkBreaks]} /> plugins={[RemarkBreaks]} />
)); ));
export default function TextContent(props) {
export default class TextContent extends Component {
render() {
const { props } = this;
const content = props.content; const content = props.content;
const group = content.text.match( const group = content.text.match(
/([~][/])?(~[a-z]{3,6})(-[a-z]{6})?([/])(([a-z0-9-])+([/-])?)+/ /([~][/])?(~[a-z]{3,6})(-[a-z]{6})?([/])(([a-z0-9-])+([/-])?)+/
); );
if ((group !== null) // matched possible chatroom const isGroupLink = ((group !== null) // matched possible chatroom
&& (group[2].length > 2) // possible ship? && (group[2].length > 2) // possible ship?
&& (urbitOb.isValidPatp(group[2]) // valid patp? && (urbitOb.isValidPatp(group[2]) // valid patp?
&& (group[0] === content.text))) { // entire message is room name? && (group[0] === content.text))) // entire message is room name?
console.log(isGroupLink);
if(isGroupLink) {
const resource = `/ship/${content.text}`;
return ( return (
<Text mx="2px" flexShrink={0} fontSize={props.fontSize ? props.fontSize : '14px'} color='black' lineHeight="tall"> <GroupLink
<Link measure={props.measure}
className="bb b--black b--white-d mono" resource={resource}
to={'/~landscape/join/' + group.input}> api={props.api}
{content.text} associations={props.associations}
</Link> groups={props.groups}
</Text> />
); );
} else { } else {
return ( return (
@ -104,4 +106,3 @@ export default class TextContent extends Component {
); );
} }
} }
}

View File

@ -0,0 +1,75 @@
import React, { useEffect, useState, useLayoutEffect } from "react";
import { Box, Text, Row, Button, Action } from "@tlon/indigo-react";
import GlobalApi from "~/logic/api/global";
import { Associations, Groups } from "~/types";
import { MetadataIcon } from "../landscape/components/MetadataIcon";
import {JoinGroup} from "../landscape/components/JoinGroup";
import {useModal} from "~/logic/lib/useModal";
export function GroupLink(props: {
api: GlobalApi;
resource: string;
associations: Associations;
groups: Groups;
measure: () => void;
}) {
const { resource, api, measure } = props;
const name = resource.slice(6);
const [preview, setPreview] = useState<MetadataUpdatePreview | null>(null);
const { modal, showModal } = useModal({
modal: (
<JoinGroup
groups={props.groups}
associations={props.associations}
api={api}
autojoin={name}
/>
)
});
const joined = resource in props.associations.groups;
useEffect(() => {
(async () => {
setPreview(await api.metadata.preview(resource));
})();
return () => {
setPreview(null);
};
}, [resource]);
useLayoutEffect(() => {
measure();
}, [preview]);
return (
<Box>
{modal}
<Row
width="fit-content"
flexShrink={1}
alignItems="center"
border="1"
borderColor="lightGray"
borderRadius="2"
py="2"
px="2"
>
{preview ? (
<>
<MetadataIcon height="4" width="4" metadata={preview.metadata} />
<Text ml="2" fontWeight="medium">{preview.metadata.title}</Text>
<Button disabled={joined} onClick={showModal} ml="4" primary>
{joined ? "Joined" : "Join"}
</Button>
</>
) : (
<Text mono>{name}</Text>
)}
</Row>
</Box>
);
}

View File

@ -45,6 +45,7 @@ interface JoinGroupProps {
groups: Groups; groups: Groups;
associations: Associations; associations: Associations;
api: GlobalApi; api: GlobalApi;
autojoin?: string;
} }
function Autojoin(props: { autojoin: string | null }) { function Autojoin(props: { autojoin: string | null }) {