Merge pull request #4801 from urbit/james/visual-grabbag

chat: jimmy's visualfix omnibus
This commit is contained in:
matildepark 2021-04-26 14:57:22 -04:00 committed by GitHub
commit 844bc1f8f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 153 additions and 41 deletions

View File

@ -12,6 +12,7 @@ import { Contacts, Content } from '@urbit/api';
import { Row, BaseImage, Box, Icon, LoadingSpinner } from '@tlon/indigo-react';
import withStorage from '~/views/components/withStorage';
import { withLocalState } from '~/logic/state/local';
import { MOBILE_BROWSER_REGEX } from "~/logic/lib/util";
type ChatInputProps = IuseStorage & {
api: GlobalApi;
@ -30,6 +31,7 @@ interface ChatInputState {
inCodeMode: boolean;
submitFocus: boolean;
uploadingPaste: boolean;
currentInput: string;
}
class ChatInput extends Component<ChatInputProps, ChatInputState> {
@ -41,7 +43,8 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
this.state = {
inCodeMode: false,
submitFocus: false,
uploadingPaste: false
uploadingPaste: false,
currentInput: props.message,
};
this.chatEditor = React.createRef();
@ -50,6 +53,7 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
this.toggleCode = this.toggleCode.bind(this);
this.uploadSuccess = this.uploadSuccess.bind(this);
this.uploadError = this.uploadError.bind(this);
this.eventHandler = this.eventHandler.bind(this);
}
toggleCode() {
@ -61,6 +65,7 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
submit(text) {
const { props, state } = this;
const [, , ship, name] = props.station.split('/');
this.setState({ currentInput: '' });
if (state.inCodeMode) {
this.setState(
{
@ -119,6 +124,14 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
.catch(this.uploadError);
});
}
toggleFocus(value) {
this.setState({ submitFocus: value });
}
eventHandler(value) {
this.setState({ currentInput: value });
}
render() {
const { props, state } = this;
@ -130,6 +143,7 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
const avatar =
props.ourContact && props.ourContact?.avatar && !props.hideAvatars ? (
<BaseImage
flexShrink={0}
src={props.ourContact.avatar}
height={24}
width={24}
@ -170,7 +184,7 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
className='cf'
zIndex={0}
>
<Row p='12px 4px 12px 12px' alignItems='center'>
<Row p='12px 4px 12px 12px' flexShrink={0} alignItems='center'>
{avatar}
</Row>
<ChatEditor
@ -180,15 +194,27 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
onUnmount={props.onUnmount}
message={props.message}
onPaste={this.onPaste.bind(this)}
focusEvent={() => this.toggleFocus(true)}
blurEvent={() => this.toggleFocus(false)}
changeEvent={this.eventHandler}
placeholder='Message...'
/>
<Box mx={2} flexShrink={0} height='16px' width='16px' flexBasis='16px'>
<Box mx='12px' flexShrink={0} height='16px' width='16px' flexBasis='16px'>
<Icon
icon='Dojo'
cursor='pointer'
onClick={this.toggleCode}
color={state.inCodeMode ? 'blue' : 'black'}
/>
</Box>
<Box ml='12px' mr={3} flexShrink={0} height='16px' width='16px' flexBasis='16px'>
{this.props.canUpload ? (
this.props.uploading ? (
<LoadingSpinner />
) : (
<Icon
icon='Attachment'
cursor='pointer'
width='16'
height='16'
onClick={() =>
@ -198,13 +224,26 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
)
) : null}
</Box>
<Box mr={2} flexShrink={0} height='16px' width='16px' flexBasis='16px'>
<Icon
icon='Dojo'
onClick={this.toggleCode}
color={state.inCodeMode ? 'blue' : 'black'}
/>
</Box>
{(MOBILE_BROWSER_REGEX.test(navigator.userAgent) &&
state.submitFocus) ||
state.currentInput !== "" ? (
<Box
ml={2}
mr="12px"
flexShrink={0}
display="flex"
justifyContent="center"
alignItems="center"
width="24px"
height="24px"
borderRadius="50%"
backgroundColor={state.currentInput !== "" ? "blue" : "gray"}
cursor={state.currentInput !== "" ? "pointer" : "default"}
onClick={() => this.chatEditor.current.submit()}
>
<Icon icon="ArrowEast" color="white" />
</Box>
) : null}
</Row>
);
}

View File

@ -578,7 +578,7 @@ export const MessagePlaceholder = ({
>
<Text
display='block'
background='gray'
background='washedGray'
width='24px'
height='24px'
borderRadius='50%'
@ -601,12 +601,13 @@ export const MessagePlaceholder = ({
display='inline-block'
verticalAlign='middle'
fontSize='0'
gray
washedGray
cursor='default'
>
<Text maxWidth='32rem' display='block'>
<Text
backgroundColor='gray'
backgroundColor='washedGray'
borderRadius='2'
display='block'
width='100%'
height='100%'
@ -618,10 +619,11 @@ export const MessagePlaceholder = ({
mono
verticalAlign='middle'
fontSize='0'
gray
washedGray
>
<Text
background='gray'
background='washedGray'
borderRadius='2'
display='block'
height='1em'
style={{ width: `${((index % 3) + 1) * 3}em` }}
@ -632,12 +634,14 @@ export const MessagePlaceholder = ({
verticalAlign='middle'
fontSize='0'
ml='2'
gray
washedGray
borderRadius='2'
display={['none', 'inline-block']}
className='child'
>
<Text
backgroundColor='gray'
backgroundColor='washedGray'
borderRadius='2'
display='block'
width='100%'
height='100%'
@ -646,7 +650,8 @@ export const MessagePlaceholder = ({
</Box>
<Text
display='block'
backgroundColor='gray'
backgroundColor='washedGray'
borderRadius='2'
height='1em'
style={{ width: `${(index % 5) * 20}%` }}
></Text>

View File

@ -162,6 +162,7 @@ export default class ChatEditor extends Component {
editor.showHint(['test', 'foo']);
}
if (this.state.message !== '' && value == '') {
this.props.changeEvent(value);
this.setState({
message: value
});
@ -169,6 +170,7 @@ export default class ChatEditor extends Component {
if (value == this.props.message || value == '' || value == ' ') {
return;
}
this.props.changeEvent(value);
this.setState({
message: value
});
@ -179,6 +181,8 @@ export default class ChatEditor extends Component {
inCodeMode,
placeholder,
message,
focusEvent,
blurEvent,
...props
} = this.props;
@ -238,6 +242,8 @@ export default class ChatEditor extends Component {
rows="1"
style={{ width: '100%', background: 'transparent', color: 'currentColor' }}
placeholder={inCodeMode ? "Code..." : "Message..."}
onFocus={focusEvent}
onBlur={blurEvent}
onChange={event => {
this.messageChange(null, null, event.target.value);
}}
@ -265,6 +271,8 @@ export default class ChatEditor extends Component {
this.editor = editor;
editor.focus();
}}
onFocus={focusEvent}
onBlur={blurEvent}
{...props}
/>
}

View File

@ -23,6 +23,7 @@ import RichText from './RichText';
import { ProfileStatus } from './ProfileStatus';
import useSettingsState from '~/logic/state/settings';
import {useOutsideClick} from '~/logic/lib/useOutsideClick';
import {useCopy} from '~/logic/lib/useCopy';
import {useContact} from '~/logic/state/contact';
import {useHistory} from 'react-router-dom';
import {Portal} from './Portal';
@ -59,6 +60,7 @@ const ProfileOverlay = (props: ProfileOverlayProps) => {
const hideAvatars = useSettingsState(state => state.calm.hideAvatars);
const hideNicknames = useSettingsState(state => state.calm.hideNicknames);
const isOwn = useMemo(() => window.ship === ship, [ship]);
const { copyDisplay, doCopy, didCopy } = useCopy(`~${ship}`);
const contact = useContact(`~${ship}`)
const color = `#${uxToHex(contact?.color ?? '0x0')}`;
@ -188,9 +190,18 @@ const ProfileOverlay = (props: ProfileOverlayProps) => {
overflow='hidden'
whiteSpace='pre'
marginBottom='0'
cursor='pointer'
display={didCopy ? 'none' : 'block'}
onClick={doCopy}
>
{showNickname ? contact?.nickname : cite(ship)}
</Text>
<Text
fontWeight='600'
marginBottom='0'
>
{copyDisplay}
</Text>
</Row>
{isOwn ? (
<ProfileStatus

View File

@ -48,12 +48,14 @@ class RemoteContent extends Component<RemoteContentProps, RemoteContentState> {
this.state = {
unfold: props.unfold || false,
embed: undefined,
noCors: false
noCors: false,
showArrow: false
};
this.unfoldEmbed = this.unfoldEmbed.bind(this);
this.loadOembed = this.loadOembed.bind(this);
this.wrapInLink = this.wrapInLink.bind(this);
this.onError = this.onError.bind(this);
this.toggleArrow = this.toggleArrow.bind(this);
}
save = () => {
@ -128,7 +130,7 @@ return;
});
}
wrapInLink(contents, textOnly = false, unfold = false, unfoldEmbed = null, embedContainer = null) {
wrapInLink(contents, textOnly = false, unfold = false, unfoldEmbed = null, embedContainer = null, flushPadding = false, noOp = false) {
const { style } = this.props;
return (
<Box borderRadius="1" backgroundColor="washedGray" maxWidth="min(100%, 20rem)">
@ -145,8 +147,8 @@ return;
)}
<BaseAnchor
display="flex"
p="2"
onClick={(e) => { e.stopPropagation(); }}
p={flushPadding ? 0 : 2}
onClick={(e) => { noOp ? e.preventDefault() : e.stopPropagation() }}
href={this.props.url}
whiteSpace="nowrap"
overflow="hidden"
@ -157,7 +159,8 @@ return;
style={{ color: 'inherit', textDecoration: 'none', ...style }}
target="_blank"
rel="noopener noreferrer"
>
cursor={noOp ? 'default' : 'pointer'}
>
{contents}
</BaseAnchor>
</Row>
@ -171,11 +174,16 @@ return;
this.setState({ noCors: true });
}
toggleArrow() {
this.setState({showArrow: !this.state.showArrow})
}
render() {
const {
remoteContentPolicy,
url,
text,
transcluded,
renderUrl = true,
imageProps = {},
audioProps = {},
@ -192,22 +200,60 @@ return;
const isVideo = VIDEO_REGEX.test(url);
const isOembed = hasProvider(url);
const isTranscluded = () => {
return transcluded;
}
if (isImage && remoteContentPolicy.imageShown) {
return this.wrapInLink(
<BaseImage
{...(noCors ? {} : { crossOrigin: "anonymous" })}
referrerPolicy="no-referrer"
flexShrink={0}
src={url}
style={style}
onLoad={onLoad}
onError={this.onError}
height="100%"
width="100%"
objectFit="contain"
{...imageProps}
{...props}
/>
<Box
position='relative'
onMouseEnter={this.toggleArrow}
onMouseLeave={this.toggleArrow}
>
<BaseAnchor
position='absolute'
top={2}
right={2}
display={this.state.showArrow ? 'block' : 'none'}
target='_blank'
rel='noopener noreferrer'
onClick={(e) => {
e.stopPropagation();
}}
href={url}
>
<Box
backgroundColor='white'
padding={2}
borderRadius='50%'
display='flex'
>
<Icon icon='ArrowNorthEast' />
</Box>
</BaseAnchor>
<BaseImage
{...(noCors ? {} : { crossOrigin: 'anonymous' })}
referrerPolicy='no-referrer'
flexShrink={0}
src={url}
style={style}
onLoad={onLoad}
onError={this.onError}
height='100%'
width='100%'
objectFit='contain'
borderRadius={2}
{...imageProps}
{...props}
/>
</Box>,
false,
false,
null,
null,
true,
isTranscluded()
);
} else if (isAudio && remoteContentPolicy.audioShown) {
return (
@ -271,7 +317,6 @@ return;
display={this.state.unfold ? 'block' : 'none'}
className='embed-container'
style={style}
flexShrink={0}
onLoad={this.onLoad}
{...oembedProps}
{...props}

View File

@ -134,7 +134,7 @@ const StatusBar = (props) => {
mr={2}
onClick={() => props.history.push('/~landscape/messages')}
>
<Icon icon='Users' />
<Icon icon='Messages' />
</StatusBarItem>
<Dropdown
dropWidth='250px'

View File

@ -130,7 +130,7 @@ export class OmniboxResult extends Component {
<Icon
display='inline-block'
verticalAlign='middle'
icon='Users'
icon='Messages'
mr='2'
size='18px'
color={iconFill}

View File

@ -58,7 +58,11 @@ function GraphContentWideInner(
width="fit-content"
maxWidth="min(500px, 100%)"
>
<RemoteContent key={content.url} url={content.url} />
<RemoteContent
key={content.url}
url={content.url}
transcluded={transcluded}
/>
</Box>
);
case "mention":