mirror of
https://github.com/urbit/shrub.git
synced 2024-12-23 19:05:48 +03:00
chat: sigil overlay exclusive of mini-sig (WIP)
Fixes urbit/landscape#303
This commit is contained in:
parent
1a6c3a1767
commit
cf0189019f
@ -1,32 +1,58 @@
|
||||
import React, { useState, useEffect, Component, PureComponent } from "react";
|
||||
import moment from "moment";
|
||||
import _ from "lodash";
|
||||
import { Box, Row, Text, Rule } from "@tlon/indigo-react";
|
||||
|
||||
import OverlaySigil from '~/views/components/OverlaySigil';
|
||||
import { uxToHex, cite, writeText, useShowNickname, useHovering } from '~/logic/lib/util';
|
||||
import { Group, Association, Contacts, Post } from "~/types";
|
||||
/* eslint-disable max-lines-per-function */
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
useRef,
|
||||
Component,
|
||||
PureComponent
|
||||
} from 'react';
|
||||
import moment from 'moment';
|
||||
import _ from 'lodash';
|
||||
import { Box, Row, Text, Rule } from '@tlon/indigo-react';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import { OverlayBox } from '~/views/components/OverlaySigil';
|
||||
import {
|
||||
uxToHex,
|
||||
cite,
|
||||
writeText,
|
||||
useShowNickname,
|
||||
useHovering
|
||||
} from '~/logic/lib/util';
|
||||
import { Group, Association, Contacts, Post } from '~/types';
|
||||
import TextContent from './content/text';
|
||||
import CodeContent from './content/code';
|
||||
import RemoteContent from '~/views/components/RemoteContent';
|
||||
import { Mention } from "~/views/components/MentionText";
|
||||
import styled from "styled-components";
|
||||
import useLocalState from "~/logic/state/local";
|
||||
import { Mention } from '~/views/components/MentionText';
|
||||
import styled from 'styled-components';
|
||||
import useLocalState from '~/logic/state/local';
|
||||
|
||||
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
||||
|
||||
export const UnreadMarker = React.forwardRef(({ dayBreak, when }, ref) => (
|
||||
<Row flexShrink={0} ref={ref} color='blue' alignItems='center' fontSize='0' position='absolute' width='100%' py='2'>
|
||||
<Row
|
||||
flexShrink={0}
|
||||
ref={ref}
|
||||
color='blue'
|
||||
alignItems='center'
|
||||
fontSize='0'
|
||||
position='absolute'
|
||||
width='100%'
|
||||
py='2'
|
||||
>
|
||||
<Rule borderColor='blue' display={['none', 'block']} m='0' width='2rem' />
|
||||
<Text flexShrink='0' display='block' zIndex='2' mx='4' color='blue'>New messages below</Text>
|
||||
<Rule borderColor='blue' flexGrow='1' m='0'/>
|
||||
<Rule style={{ width: "calc(50% - 48px)" }} borderColor='blue' m='0' />
|
||||
<Text flexShrink='0' display='block' zIndex='2' mx='4' color='blue'>
|
||||
New messages below
|
||||
</Text>
|
||||
<Rule borderColor='blue' flexGrow='1' m='0' />
|
||||
<Rule style={{ width: 'calc(50% - 48px)' }} borderColor='blue' m='0' />
|
||||
</Row>
|
||||
));
|
||||
|
||||
export const DayBreak = ({ when }) => (
|
||||
<Row pb='3' alignItems="center" justifyContent="center" width='100%'>
|
||||
<Text gray>{moment(when).calendar(null, { sameElse: DATESTAMP_FORMAT })}</Text>
|
||||
<Row pb='3' alignItems='center' justifyContent='center' width='100%'>
|
||||
<Text gray>
|
||||
{moment(when).calendar(null, { sameElse: DATESTAMP_FORMAT })}
|
||||
</Text>
|
||||
</Row>
|
||||
);
|
||||
|
||||
@ -86,14 +112,21 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
fontSize
|
||||
} = this.props;
|
||||
|
||||
const renderSigil = Boolean((nextMsg && msg.author !== nextMsg.author) || !nextMsg || msg.number === 1);
|
||||
const dayBreak = nextMsg && new Date(msg['time-sent']).getDate() !== new Date(nextMsg['time-sent']).getDate();
|
||||
const renderSigil = Boolean(
|
||||
(nextMsg && msg.author !== nextMsg.author) || !nextMsg || msg.number === 1
|
||||
);
|
||||
const dayBreak =
|
||||
nextMsg &&
|
||||
new Date(msg['time-sent']).getDate() !==
|
||||
new Date(nextMsg['time-sent']).getDate();
|
||||
|
||||
const containerClass = `${renderSigil
|
||||
? `cf pl2 lh-copy`
|
||||
: `items-top cf hide-child`} ${isPending ? 'o-40' : ''} ${className}`
|
||||
const containerClass = `${
|
||||
renderSigil ? 'cf pl2 lh-copy' : 'items-top cf hide-child'
|
||||
} ${isPending ? 'o-40' : ''} ${className}`;
|
||||
|
||||
const timestamp = moment.unix(msg['time-sent'] / 1000).format(renderSigil ? 'hh:mm a' : 'hh:mm');
|
||||
const timestamp = moment
|
||||
.unix(msg['time-sent'] / 1000)
|
||||
.format(renderSigil ? 'hh:mm a' : 'hh:mm');
|
||||
|
||||
const reboundMeasure = (event) => {
|
||||
return measure(this.divRef.current);
|
||||
@ -117,7 +150,7 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
};
|
||||
|
||||
const unreadContainerStyle = {
|
||||
height: isLastRead ? '2rem' : '0',
|
||||
height: isLastRead ? '2rem' : '0'
|
||||
};
|
||||
|
||||
return (
|
||||
@ -134,15 +167,30 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
className={containerClass}
|
||||
style={style}
|
||||
mb={1}
|
||||
position="relative"
|
||||
position='relative'
|
||||
>
|
||||
{dayBreak && !isLastRead ? <DayBreak when={msg['time-sent']} /> : null}
|
||||
{renderSigil
|
||||
? <MessageWithSigil {...messageProps} />
|
||||
: <MessageWithoutSigil {...messageProps} />}
|
||||
<Box flexShrink={0} fontSize={0} position='relative' width='100%' overflow='visible' style={unreadContainerStyle}>{isLastRead
|
||||
? <UnreadMarker dayBreak={dayBreak} when={msg['time-sent']} ref={unreadMarkerRef} />
|
||||
: null}</Box>
|
||||
{renderSigil ? (
|
||||
<MessageWithSigil {...messageProps} />
|
||||
) : (
|
||||
<MessageWithoutSigil {...messageProps} />
|
||||
)}
|
||||
<Box
|
||||
flexShrink={0}
|
||||
fontSize={0}
|
||||
position='relative'
|
||||
width='100%'
|
||||
overflow='visible'
|
||||
style={unreadContainerStyle}
|
||||
>
|
||||
{isLastRead ? (
|
||||
<UnreadMarker
|
||||
dayBreak={dayBreak}
|
||||
when={msg['time-sent']}
|
||||
ref={unreadMarkerRef}
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@ -159,7 +207,7 @@ interface MessageProps {
|
||||
style: any;
|
||||
measure(element): void;
|
||||
scrollWindow: HTMLDivElement;
|
||||
};
|
||||
}
|
||||
|
||||
export const MessageWithSigil = (props) => {
|
||||
const {
|
||||
@ -175,17 +223,27 @@ export const MessageWithSigil = (props) => {
|
||||
fontSize
|
||||
} = props;
|
||||
|
||||
const dark = useLocalState(state => state.dark);
|
||||
const dark = useLocalState((state) => state.dark);
|
||||
|
||||
const datestamp = moment.unix(msg['time-sent'] / 1000).format(DATESTAMP_FORMAT);
|
||||
const datestamp = moment
|
||||
.unix(msg['time-sent'] / 1000)
|
||||
.format(DATESTAMP_FORMAT);
|
||||
const contact = msg.author in contacts ? contacts[msg.author] : false;
|
||||
const showNickname = useShowNickname(contact);
|
||||
const shipName = showNickname ? contact.nickname : cite(msg.author);
|
||||
const copyNotice = 'Copied';
|
||||
const color = contact ? `#${uxToHex(contact.color)}` : dark ? '#000000' :'#FFFFFF'
|
||||
const sigilClass = contact ? '' : dark ? 'mix-blend-diff' : 'mix-blend-darken';
|
||||
const color = contact
|
||||
? `#${uxToHex(contact.color)}`
|
||||
: dark
|
||||
? '#000000'
|
||||
: '#FFFFFF';
|
||||
const sigilClass = contact
|
||||
? ''
|
||||
: dark
|
||||
? 'mix-blend-diff'
|
||||
: 'mix-blend-darken';
|
||||
const [displayName, setDisplayName] = useState(shipName);
|
||||
const [nameMono, setNameMono] = useState((showNickname ? false : true));
|
||||
const [nameMono, setNameMono] = useState(showNickname ? false : true);
|
||||
const { hovering, bind } = useHovering();
|
||||
|
||||
const showCopyNotice = () => {
|
||||
@ -196,7 +254,7 @@ export const MessageWithSigil = (props) => {
|
||||
useEffect(() => {
|
||||
const resetDisplay = () => {
|
||||
setDisplayName(shipName);
|
||||
setNameMono((showNickname ? false : true));
|
||||
setNameMono(showNickname ? false : true);
|
||||
};
|
||||
const timer = setTimeout(() => resetDisplay(), 800);
|
||||
return () => clearTimeout(timer);
|
||||
@ -204,24 +262,20 @@ export const MessageWithSigil = (props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<OverlaySigil
|
||||
ship={msg.author}
|
||||
contact={contact}
|
||||
color={color}
|
||||
sigilClass={sigilClass}
|
||||
group={group}
|
||||
scrollWindow={scrollWindow}
|
||||
history={history}
|
||||
api={api}
|
||||
bg="white"
|
||||
className="fl v-top pt1"
|
||||
pr={3}
|
||||
pl={2}
|
||||
/>
|
||||
<Box flexGrow={1} display='block' className="clamp-message" {...bind}>
|
||||
<Box mr={12} ml={12} pt={1} pb={1} height={16}>
|
||||
<Sigil
|
||||
ship={ship}
|
||||
size={16}
|
||||
color={color}
|
||||
classes={sigilClass}
|
||||
icon
|
||||
padded
|
||||
/>
|
||||
</Box>
|
||||
<Box flexGrow={1} display='block' className='clamp-message' {...bind}>
|
||||
<Box
|
||||
flexShrink={0}
|
||||
className="hide-child"
|
||||
className='hide-child'
|
||||
pt={1}
|
||||
pb={1}
|
||||
display='flex'
|
||||
@ -233,7 +287,7 @@ export const MessageWithSigil = (props) => {
|
||||
flexShrink={0}
|
||||
mono={nameMono}
|
||||
fontWeight={nameMono ? '400' : '500'}
|
||||
className={`mw5 db truncate pointer`}
|
||||
className={'mw5 db truncate pointer'}
|
||||
onClick={() => {
|
||||
writeText(`~${msg.author}`);
|
||||
showCopyNotice();
|
||||
@ -242,7 +296,9 @@ export const MessageWithSigil = (props) => {
|
||||
>
|
||||
{displayName}
|
||||
</Text>
|
||||
<Text flexShrink={0} fontSize={0} gray mono>{timestamp}</Text>
|
||||
<Text flexShrink={0} fontSize={0} gray mono>
|
||||
{timestamp}
|
||||
</Text>
|
||||
<Text
|
||||
flexShrink={0}
|
||||
fontSize={0}
|
||||
@ -250,32 +306,42 @@ export const MessageWithSigil = (props) => {
|
||||
mono
|
||||
ml={2}
|
||||
display={['none', hovering ? 'block' : 'none']}
|
||||
>{datestamp}</Text>
|
||||
>
|
||||
{datestamp}
|
||||
</Text>
|
||||
</Box>
|
||||
<ContentBox flexShrink={0} fontSize={fontSize ? fontSize : '14px'}>
|
||||
{msg.contents.map(c =>
|
||||
<MessageContent
|
||||
contacts={contacts}
|
||||
content={c}
|
||||
measure={measure}
|
||||
fontSize={fontSize}
|
||||
group={group}
|
||||
/>)}
|
||||
{msg.contents.map((c, i) => (
|
||||
<MessageContent
|
||||
key={i}
|
||||
contacts={contacts}
|
||||
content={c}
|
||||
measure={measure}
|
||||
scrollWindow={scrollWindow}
|
||||
fontSize={fontSize}
|
||||
group={group}
|
||||
/>
|
||||
))}
|
||||
</ContentBox>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const ContentBox = styled(Box)`
|
||||
& > :first-child {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
export const MessageWithoutSigil = ({ timestamp, contacts, msg, measure, group }) => {
|
||||
export const MessageWithoutSigil = ({
|
||||
timestamp,
|
||||
contacts,
|
||||
msg,
|
||||
measure,
|
||||
group,
|
||||
scrollWindow
|
||||
}) => {
|
||||
const { hovering, bind } = useHovering();
|
||||
return (
|
||||
<>
|
||||
@ -283,17 +349,19 @@ export const MessageWithoutSigil = ({ timestamp, contacts, msg, measure, group }
|
||||
flexShrink={0}
|
||||
mono
|
||||
gray
|
||||
display={hovering ? 'block': 'none'}
|
||||
display={hovering ? 'block' : 'none'}
|
||||
pt='2px'
|
||||
lineHeight='tall'
|
||||
fontSize={0}
|
||||
position="absolute"
|
||||
position='absolute'
|
||||
left={1}
|
||||
>{timestamp}</Text>
|
||||
>
|
||||
{timestamp}
|
||||
</Text>
|
||||
<ContentBox
|
||||
flexShrink={0}
|
||||
fontSize='14px'
|
||||
className="clamp-message"
|
||||
className='clamp-message'
|
||||
style={{ flexGrow: 1 }}
|
||||
{...bind}
|
||||
pl={6}
|
||||
@ -304,52 +372,87 @@ export const MessageWithoutSigil = ({ timestamp, contacts, msg, measure, group }
|
||||
contacts={contacts}
|
||||
content={c}
|
||||
group={group}
|
||||
measure={measure}/>))}
|
||||
measure={measure}
|
||||
scrollWindow={scrollWindow}
|
||||
/>
|
||||
))}
|
||||
</ContentBox>
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export const MessageContent = ({ content, contacts, measure, fontSize, group }) => {
|
||||
export const MessageContent = ({
|
||||
content,
|
||||
contacts,
|
||||
measure,
|
||||
scrollWindow,
|
||||
fontSize,
|
||||
group
|
||||
}) => {
|
||||
if ('code' in content) {
|
||||
return <CodeContent content={content} />;
|
||||
} else if ('url' in content) {
|
||||
return (
|
||||
<Box mx="2px" flexShrink={0} fontSize={fontSize ? fontSize : '14px'} lineHeight="tall" color='black'>
|
||||
<Box
|
||||
mx='2px'
|
||||
flexShrink={0}
|
||||
fontSize={fontSize ? fontSize : '14px'}
|
||||
lineHeight='tall'
|
||||
color='black'
|
||||
>
|
||||
<RemoteContent
|
||||
url={content.url}
|
||||
onLoad={measure}
|
||||
imageProps={{style: {
|
||||
maxWidth: 'min(100%,18rem)',
|
||||
display: 'block'
|
||||
}}}
|
||||
videoProps={{style: {
|
||||
maxWidth: '18rem',
|
||||
display: 'block'
|
||||
}
|
||||
imageProps={{
|
||||
style: {
|
||||
maxWidth: 'min(100%,18rem)',
|
||||
display: 'block'
|
||||
}
|
||||
}}
|
||||
videoProps={{
|
||||
style: {
|
||||
maxWidth: '18rem',
|
||||
display: 'block'
|
||||
}
|
||||
}}
|
||||
textProps={{
|
||||
style: {
|
||||
fontSize: 'inherit',
|
||||
borderBottom: '1px solid',
|
||||
textDecoration: 'none'
|
||||
}
|
||||
}}
|
||||
textProps={{style: {
|
||||
fontSize: 'inherit',
|
||||
borderBottom: '1px solid',
|
||||
textDecoration: 'none'
|
||||
}}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
} else if ('text' in content) {
|
||||
return <TextContent fontSize={fontSize} content={content} />;
|
||||
} else if ('mention' in content) {
|
||||
return <Mention group={group} ship={content.mention} contact={contacts?.[content.mention]} />
|
||||
return (
|
||||
<Mention
|
||||
group={group}
|
||||
scrollWindow={scrollWindow}
|
||||
ship={content.mention}
|
||||
contact={contacts?.[content.mention]}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const MessagePlaceholder = ({ height, index, className = '', style = {}, ...props }) => (
|
||||
export const MessagePlaceholder = ({
|
||||
height,
|
||||
index,
|
||||
className = '',
|
||||
style = {},
|
||||
...props
|
||||
}) => (
|
||||
<Box
|
||||
width='100%'
|
||||
fontSize='2'
|
||||
pl='3' pt='4'
|
||||
pl='3'
|
||||
pt='4'
|
||||
pr='3'
|
||||
display='flex'
|
||||
lineHeight='tall'
|
||||
@ -357,78 +460,86 @@ export const MessagePlaceholder = ({ height, index, className = '', style = {},
|
||||
style={{ height, ...style }}
|
||||
{...props}
|
||||
>
|
||||
<Box pr='3' verticalAlign='top' backgroundColor='white' style={{ float: 'left' }}>
|
||||
<Text
|
||||
display='block'
|
||||
background='gray'
|
||||
width='24px'
|
||||
height='24px'
|
||||
borderRadius='50%'
|
||||
style={{
|
||||
visibility: (index % 5 == 0) ? "initial" : "hidden",
|
||||
}}
|
||||
></Text>
|
||||
</Box>
|
||||
<Box
|
||||
pr='3'
|
||||
verticalAlign='top'
|
||||
backgroundColor='white'
|
||||
style={{ float: 'left' }}
|
||||
>
|
||||
<Text
|
||||
display='block'
|
||||
background='gray'
|
||||
width='24px'
|
||||
height='24px'
|
||||
borderRadius='50%'
|
||||
style={{
|
||||
visibility: index % 5 == 0 ? 'initial' : 'hidden'
|
||||
}}
|
||||
></Text>
|
||||
</Box>
|
||||
<Box
|
||||
style={{ float: 'right', flexGrow: 1 }}
|
||||
color='black'
|
||||
className='clamp-message'
|
||||
>
|
||||
<Box
|
||||
style={{ float: 'right', flexGrow: 1 }}
|
||||
color='black'
|
||||
className="clamp-message"
|
||||
className='hide-child'
|
||||
paddingTop='4'
|
||||
style={{ visibility: index % 5 == 0 ? 'initial' : 'hidden' }}
|
||||
>
|
||||
<Box
|
||||
className="hide-child"
|
||||
paddingTop='4'
|
||||
style={{visibility: (index % 5 == 0) ? "initial" : "hidden" }}
|
||||
<Text
|
||||
display='inline-block'
|
||||
verticalAlign='middle'
|
||||
fontSize='0'
|
||||
gray
|
||||
cursor='default'
|
||||
>
|
||||
<Text
|
||||
display='inline-block'
|
||||
verticalAlign='middle'
|
||||
fontSize='0'
|
||||
gray
|
||||
cursor='default'
|
||||
>
|
||||
<Text maxWidth='32rem' display='block'>
|
||||
<Text
|
||||
backgroundColor='gray'
|
||||
display='block'
|
||||
width='100%'
|
||||
height='100%'></Text>
|
||||
</Text>
|
||||
</Text>
|
||||
<Text
|
||||
display='inline-block'
|
||||
mono
|
||||
verticalAlign='middle'
|
||||
fontSize='0'
|
||||
gray
|
||||
>
|
||||
<Text
|
||||
background='gray'
|
||||
display='block'
|
||||
height='1em'
|
||||
style={{ width: `${(index % 3 + 1) * 3}em` }}
|
||||
></Text>
|
||||
</Text>
|
||||
<Text
|
||||
mono
|
||||
verticalAlign='middle'
|
||||
fontSize='0'
|
||||
ml='2'
|
||||
gray
|
||||
display={['none', 'inline-block']}
|
||||
className="child">
|
||||
<Text maxWidth='32rem' display='block'>
|
||||
<Text
|
||||
backgroundColor='gray'
|
||||
display='block'
|
||||
width='100%'
|
||||
height='100%'
|
||||
></Text>
|
||||
</Text>
|
||||
</Box>
|
||||
></Text>
|
||||
</Text>
|
||||
</Text>
|
||||
<Text
|
||||
display='block'
|
||||
backgroundColor='gray'
|
||||
height='1em'
|
||||
style={{ width: `${(index % 5) * 20}%` }}></Text>
|
||||
display='inline-block'
|
||||
mono
|
||||
verticalAlign='middle'
|
||||
fontSize='0'
|
||||
gray
|
||||
>
|
||||
<Text
|
||||
background='gray'
|
||||
display='block'
|
||||
height='1em'
|
||||
style={{ width: `${((index % 3) + 1) * 3}em` }}
|
||||
></Text>
|
||||
</Text>
|
||||
<Text
|
||||
mono
|
||||
verticalAlign='middle'
|
||||
fontSize='0'
|
||||
ml='2'
|
||||
gray
|
||||
display={['none', 'inline-block']}
|
||||
className='child'
|
||||
>
|
||||
<Text
|
||||
backgroundColor='gray'
|
||||
display='block'
|
||||
width='100%'
|
||||
height='100%'
|
||||
></Text>
|
||||
</Text>
|
||||
</Box>
|
||||
<Text
|
||||
display='block'
|
||||
backgroundColor='gray'
|
||||
height='1em'
|
||||
style={{ width: `${(index % 5) * 20}%` }}
|
||||
></Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
@ -1,16 +1,13 @@
|
||||
import React, { useState, useCallback } from "react";
|
||||
import _ from "lodash";
|
||||
import { Text, Box } from "@tlon/indigo-react";
|
||||
import {
|
||||
Contact,
|
||||
Contacts,
|
||||
Content,
|
||||
Group,
|
||||
} from "~/types";
|
||||
import RichText from "~/views/components/RichText";
|
||||
import { cite, useShowNickname, uxToHex } from "~/logic/lib/util";
|
||||
import ProfileOverlay from "./ProfileOverlay";
|
||||
import { useHistory } from "react-router-dom";
|
||||
/* eslint-disable max-lines-per-function */
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { Text, Box } from '@tlon/indigo-react';
|
||||
import { Contact, Contacts, Content, Group } from '~/types';
|
||||
import RichText from '~/views/components/RichText';
|
||||
import { cite, useShowNickname, uxToHex } from '~/logic/lib/util';
|
||||
// import ProfileOverlay from './ProfileOverlay';
|
||||
import OverlaySigil, { OverlayBox } from '~/views/components/OverlaySigil';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
interface MentionTextProps {
|
||||
contact?: Contact;
|
||||
@ -24,9 +21,9 @@ export function MentionText(props: MentionTextProps) {
|
||||
return (
|
||||
<RichText contacts={contacts} contact={contact} group={group} {...rest}>
|
||||
{content.reduce((accum, c) => {
|
||||
if ("text" in c) {
|
||||
if ('text' in c) {
|
||||
return accum + c.text;
|
||||
} else if ("mention" in c) {
|
||||
} else if ('mention' in c) {
|
||||
return accum + `[~${c.mention}]`;
|
||||
}
|
||||
return accum;
|
||||
@ -36,12 +33,13 @@ export function MentionText(props: MentionTextProps) {
|
||||
}
|
||||
|
||||
export function Mention(props: {
|
||||
ship: string;
|
||||
contact: Contact;
|
||||
contacts?: Contacts;
|
||||
group: Group;
|
||||
scrollWindow?: HTMLElement;
|
||||
ship: string;
|
||||
}) {
|
||||
const { contacts, ship } = props;
|
||||
const { contacts, ship, scrollWindow } = props;
|
||||
let { contact } = props;
|
||||
|
||||
contact = (contact?.color) ? contact : contacts?.[ship];
|
||||
@ -50,43 +48,38 @@ export function Mention(props: {
|
||||
|
||||
const name = showNickname ? contact?.nickname : cite(ship);
|
||||
const [showOverlay, setShowOverlay] = useState(false);
|
||||
const onDismiss = useCallback(() => {
|
||||
setShowOverlay(false);
|
||||
}, [setShowOverlay]);
|
||||
const onClick = useCallback(() => {
|
||||
setShowOverlay(true);
|
||||
}, [setShowOverlay]);
|
||||
|
||||
const group = props.group ?? { hidden: true };
|
||||
const toggleOverlay = () => {
|
||||
setShowOverlay((value) => !value);
|
||||
};
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
return (
|
||||
<Box
|
||||
position="relative"
|
||||
display="inline-block"
|
||||
cursor="pointer"
|
||||
>
|
||||
{showOverlay && (
|
||||
<ProfileOverlay
|
||||
ship={ship}
|
||||
contact={contact}
|
||||
color={`#${uxToHex(contact?.color ?? '0x0')}`}
|
||||
group={group}
|
||||
onDismiss={onDismiss}
|
||||
history={history}
|
||||
/>
|
||||
)}
|
||||
<Box position='relative' display='inline-block' cursor='pointer'>
|
||||
<Text
|
||||
onClick={onClick}
|
||||
mx="2px"
|
||||
px="2px"
|
||||
bg="washedBlue"
|
||||
color="blue"
|
||||
onClick={() => toggleOverlay()}
|
||||
mx='2px'
|
||||
px='2px'
|
||||
bg='washedBlue'
|
||||
color='blue'
|
||||
mono={!showNickname}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
{showOverlay && (
|
||||
<OverlayBox
|
||||
ship={ship}
|
||||
contact={contact}
|
||||
color={`#${uxToHex(contact?.color ?? '0x0')}`}
|
||||
group={group}
|
||||
onDismiss={() => toggleOverlay()}
|
||||
history={history}
|
||||
className='relative'
|
||||
scrollWindow={scrollWindow}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
/* eslint-disable max-lines-per-function */
|
||||
import React, { useState, useRef, useEffect, PureComponent } from 'react';
|
||||
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import { Contact, Group } from '~/types';
|
||||
@ -9,17 +10,17 @@ import { Box, BaseImage, ColProps } from '@tlon/indigo-react';
|
||||
import { withLocalState } from '~/logic/state/local';
|
||||
|
||||
type OverlaySigilProps = ColProps & {
|
||||
ship: string;
|
||||
contact?: Contact;
|
||||
color: string;
|
||||
sigilClass: string;
|
||||
group?: Group;
|
||||
scrollWindow?: HTMLElement;
|
||||
history: any;
|
||||
api: any;
|
||||
className: string;
|
||||
color: string;
|
||||
contact?: Contact;
|
||||
group?: Group;
|
||||
hideAvatars: boolean;
|
||||
}
|
||||
history: any;
|
||||
scrollWindow?: HTMLElement;
|
||||
ship: string;
|
||||
sigilClass: string;
|
||||
};
|
||||
|
||||
interface OverlaySigilState {
|
||||
clicked: boolean;
|
||||
@ -27,6 +28,80 @@ interface OverlaySigilState {
|
||||
bottomSpace: number | 'auto';
|
||||
}
|
||||
|
||||
export const OverlayBox = (props) => {
|
||||
const {
|
||||
api,
|
||||
className,
|
||||
color,
|
||||
contact,
|
||||
group,
|
||||
hideAvatars,
|
||||
history,
|
||||
onDismiss,
|
||||
scrollWindow,
|
||||
ship,
|
||||
sigilClass,
|
||||
...rest
|
||||
} = {
|
||||
...props
|
||||
};
|
||||
const containerRef = useRef(null);
|
||||
|
||||
const [space, setSpace] = useState({ top: 'auto', bottom: 'auto' });
|
||||
|
||||
const updateContainerOffset = () => {
|
||||
if (containerRef && containerRef.current) {
|
||||
const container = containerRef.current;
|
||||
const bottomSpace = scrollWindow
|
||||
? scrollWindow.clientHeight -
|
||||
(container.getBoundingClientRect().top +
|
||||
OVERLAY_HEIGHT -
|
||||
scrollWindow.getBoundingClientRect().top)
|
||||
: 'auto';
|
||||
const topSpace = scrollWindow
|
||||
? container.getBoundingClientRect().top -
|
||||
scrollWindow.getBoundingClientRect().top
|
||||
: 0;
|
||||
setSpace({
|
||||
top: topSpace,
|
||||
bottom: bottomSpace
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updateContainerOffset();
|
||||
scrollWindow.addEventListener('scroll', updateContainerOffset);
|
||||
return () => {
|
||||
scrollWindow.removeEventListener('scroll', updateContainerOffset, true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box
|
||||
cursor='pointer'
|
||||
position='relative'
|
||||
className={className}
|
||||
pr={0}
|
||||
pl={0}
|
||||
ref={containerRef}
|
||||
>
|
||||
<ProfileOverlay
|
||||
api={api}
|
||||
bottomSpace={space.bottom}
|
||||
color={color}
|
||||
contact={contact}
|
||||
group={group}
|
||||
history={history}
|
||||
onDismiss={() => onDismiss()}
|
||||
ship={ship}
|
||||
topSpace={space.top}
|
||||
{...rest}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
class OverlaySigil extends PureComponent<OverlaySigilProps, OverlaySigilState> {
|
||||
public containerRef: React.Ref<HTMLDivElement>;
|
||||
|
||||
@ -48,20 +123,35 @@ class OverlaySigil extends PureComponent<OverlaySigilProps, OverlaySigilState> {
|
||||
profileShow() {
|
||||
this.updateContainerOffset();
|
||||
this.setState({ clicked: true });
|
||||
this.props.scrollWindow?.addEventListener('scroll', this.updateContainerOffset);
|
||||
this.props.scrollWindow?.addEventListener(
|
||||
'scroll',
|
||||
this.updateContainerOffset
|
||||
);
|
||||
}
|
||||
|
||||
profileHide() {
|
||||
this.setState({ clicked: false });
|
||||
this.props.scrollWindow?.removeEventListener('scroll', this.updateContainerOffset, true);
|
||||
this.props.scrollWindow?.removeEventListener(
|
||||
'scroll',
|
||||
this.updateContainerOffset,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
updateContainerOffset() {
|
||||
if (this.containerRef && this.containerRef.current) {
|
||||
const container = this.containerRef.current;
|
||||
const scrollWindow = this.props.scrollWindow;
|
||||
const bottomSpace = scrollWindow ? scrollWindow.clientHeight - ((container.getBoundingClientRect().top + OVERLAY_HEIGHT) - scrollWindow.getBoundingClientRect().top) : 'auto';
|
||||
const topSpace = scrollWindow ? container.getBoundingClientRect().top - scrollWindow.getBoundingClientRect().top : 0;
|
||||
const bottomSpace = scrollWindow
|
||||
? scrollWindow.clientHeight -
|
||||
(container.getBoundingClientRect().top +
|
||||
OVERLAY_HEIGHT -
|
||||
scrollWindow.getBoundingClientRect().top)
|
||||
: 'auto';
|
||||
const topSpace = scrollWindow
|
||||
? container.getBoundingClientRect().top -
|
||||
scrollWindow.getBoundingClientRect().top
|
||||
: 0;
|
||||
this.setState({
|
||||
topSpace,
|
||||
bottomSpace
|
||||
@ -70,7 +160,11 @@ class OverlaySigil extends PureComponent<OverlaySigilProps, OverlaySigilState> {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.scrollWindow?.removeEventListener('scroll', this.updateContainerOffset, true);
|
||||
this.props.scrollWindow?.removeEventListener(
|
||||
'scroll',
|
||||
this.updateContainerOffset,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -91,26 +185,34 @@ class OverlaySigil extends PureComponent<OverlaySigilProps, OverlaySigilState> {
|
||||
|
||||
const { state } = this;
|
||||
|
||||
const img = (contact && (contact.avatar !== null) && !hideAvatars)
|
||||
? <BaseImage display='inline-block' src={contact.avatar} height={16} width={16} />
|
||||
: <Sigil
|
||||
ship={ship}
|
||||
size={16}
|
||||
color={color}
|
||||
classes={sigilClass}
|
||||
icon
|
||||
padded
|
||||
/>;
|
||||
const img =
|
||||
contact && contact.avatar !== null && !hideAvatars ? (
|
||||
<BaseImage
|
||||
display='inline-block'
|
||||
src={contact.avatar}
|
||||
height={16}
|
||||
width={16}
|
||||
/>
|
||||
) : (
|
||||
<Sigil
|
||||
ship={ship}
|
||||
size={16}
|
||||
color={color}
|
||||
classes={sigilClass}
|
||||
icon
|
||||
padded
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
return (
|
||||
<Box
|
||||
cursor='pointer'
|
||||
position='relative'
|
||||
onClick={this.profileShow}
|
||||
ref={this.containerRef}
|
||||
className={className}
|
||||
pr={pr}
|
||||
pl={pl}
|
||||
ref={this.containerRef}
|
||||
className={className}
|
||||
pr={pr}
|
||||
pl={pl}
|
||||
>
|
||||
{state.clicked && (
|
||||
<ProfileOverlay
|
||||
@ -122,8 +224,8 @@ class OverlaySigil extends PureComponent<OverlaySigilProps, OverlaySigilState> {
|
||||
group={group}
|
||||
onDismiss={this.profileHide}
|
||||
history={history}
|
||||
api={api}
|
||||
{...rest}
|
||||
api={api}
|
||||
{...rest}
|
||||
/>
|
||||
)}
|
||||
{img}
|
||||
|
Loading…
Reference in New Issue
Block a user