mirror of
https://github.com/urbit/shrub.git
synced 2024-12-11 11:02:25 +03:00
Merge pull request #4801 from urbit/james/visual-grabbag
chat: jimmy's visualfix omnibus
This commit is contained in:
commit
844bc1f8f4
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
/>
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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'
|
||||
|
@ -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}
|
||||
|
@ -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":
|
||||
|
Loading…
Reference in New Issue
Block a user