Merge remote-tracking branch 'origin/release/next-js' into lf/notif-v2

This commit is contained in:
Liam Fitzgerald 2021-04-16 16:03:07 +10:00
commit d5bbc58c2b
No known key found for this signature in database
GPG Key ID: D390E12C61D1CFFB
8 changed files with 572 additions and 299 deletions

View File

@ -5,6 +5,20 @@ let
set -eu
# set defaults
amesPort=${toString amesPort}
# check args
for i in "$@"
do
case $i in
-p=*|--port=*)
amesPort="''${i#*=}"
shift
;;
esac
done
# If the container is not started with the `-i` flag
# then STDIN will be closed and we need to start
# Urbit/vere with the `-t` flag.
@ -23,7 +37,7 @@ let
mv $keyname /tmp
# Boot urbit with the key, exit when done booting
urbit $ttyflag -w $(basename $keyname .key) -k /tmp/$keyname -c $(basename $keyname .key) -p ${toString amesPort} -x
urbit $ttyflag -w $(basename $keyname .key) -k /tmp/$keyname -c $(basename $keyname .key) -p $amesPort -x
# Remove the keyfile for security
rm /tmp/$keyname
@ -34,7 +48,7 @@ let
cometname=''${comets[0]}
rm *.comet
urbit $ttyflag -c $(basename $cometname .comet) -p ${toString amesPort} -x
urbit $ttyflag -c $(basename $cometname .comet) -p $amesPort -x
fi
# Find the first directory and start urbit with the ship therein
@ -42,7 +56,7 @@ let
dirs=( $dirnames )
dirname=''${dirnames[0]}
urbit $ttyflag -p ${toString amesPort} $dirname
exec urbit $ttyflag -p $amesPort $dirname
'';

View File

@ -64,20 +64,20 @@ export function ProfileHeaderImageEdit(props: any): ReactElement {
{contact?.cover ? (
<div>
{editCover ? (
<ImageInput id='cover' marginTop='-8px' />
<ImageInput id='cover' marginTop='-8px' width='288px' />
) : (
<Row>
<Button mr='2' onClick={() => setEditCover(true)}>
Replace Header
</Button>
<Button onClick={(e) => handleClear(e)}>
<Button onClick={e => handleClear(e)}>
{removedCoverLabel}
</Button>
</Row>
)}
</div>
) : (
<ImageInput id='cover' marginTop='-8px' />
<ImageInput id='cover' marginTop='-8px' width='288px' />
)}
</>
);

View File

@ -1,32 +1,36 @@
import React, { ReactElement } from 'react';
import {
Box,
Text,
Row,
Label,
Col,
ManagedRadioButtonField as Radio,
ManagedRadioButtonField as Radio
} from '@tlon/indigo-react';
import GlobalApi from '~/logic/api/global';
import { ImageInput } from '~/views/components/ImageInput';
import { ColorInput } from '~/views/components/ColorInput';
import { StorageState } from '~/types';
export type BgType = 'none' | 'url' | 'color';
export function BackgroundPicker({
bgType,
bgUrl,
api,
api
}: {
bgType: BgType;
bgUrl?: string;
api: GlobalApi;
}): ReactElement {
const rowSpace = { my: 0, alignItems: 'center' };
const colProps = { my: 3, mr: 4, gapY: 1 };
const colProps = {
my: 3,
mr: 4,
gapY: 1,
minWidth: '266px',
width: ['100%', '288px']
};
return (
<Col>
<Label>Landscape Background</Label>
@ -40,7 +44,7 @@ export function BackgroundPicker({
id="bgUrl"
placeholder="Drop or upload a file, or paste a link here"
name="bgUrl"
url={bgUrl || ""}
url={bgUrl || ''}
/>
</Col>
</Row>
@ -48,13 +52,13 @@ export function BackgroundPicker({
<Col {...colProps}>
<Radio mb="1" label="Color" id="color" name="bgType" />
<Text ml="5" gray>Set a hex-based background</Text>
<ColorInput placeholder="FFFFFF" ml="5" id="bgColor" />
<ColorInput placeholder="FFFFFF" ml="5" id="bgColor" />
</Col>
</Row>
<Radio
my="3"
caption="Your home screen will simply render as its respective day/night mode color"
name="bgType"
name="bgType"
label="None"
id="none" />
</Col>

View File

@ -7,11 +7,11 @@ import {
Row,
Button,
Label,
ErrorLabel,
BaseInput
BaseInput,
Text,
Icon
} from '@tlon/indigo-react';
import { StorageState } from '~/types';
import useStorage from '~/logic/lib/useStorage';
type ImageInputProps = Parameters<typeof Box>[0] & {
@ -20,13 +20,100 @@ type ImageInputProps = Parameters<typeof Box>[0] & {
placeholder?: string;
};
const prompt = (field, uploading, meta, clickUploadButton) => {
if (!field.value && !uploading && meta.error === undefined) {
return (
<Text
black
fontWeight='500'
position='absolute'
left={2}
top={2}
style={{ pointerEvents: 'none' }}
>
Paste a link here, or{' '}
<Text
fontWeight='500'
cursor='pointer'
color='blue'
style={{ pointerEvents: 'all' }}
onClick={clickUploadButton}
>
upload
</Text>{' '}
a file
</Text>
);
}
return null;
};
const uploadingStatus = (uploading, meta) => {
if (uploading && meta.error === undefined) {
return (
<Text position='absolute' left={2} top={2} gray>
Uploading...
</Text>
);
}
return null;
};
const errorRetry = (meta, uploading, clickUploadButton) => {
if (meta.error !== undefined) {
return (
<Text
position='absolute'
left={2}
top={2}
color='red'
style={{ pointerEvents: 'none' }}
>
{meta.error}{', '}please{' '}
<Text
fontWeight='500'
cursor='pointer'
color='blue'
style={{ pointerEvents: 'all' }}
onClick={clickUploadButton}
>
retry
</Text>
</Text>
);
}
return null;
};
const clearButton = (field, uploading, clearEvt) => {
if (field.value && !uploading) {
return (
<Box
position='absolute'
right={0}
top={0}
px={1}
height='100%'
cursor='pointer'
onClick={clearEvt}
backgroundColor='white'
display='flex'
alignItems='center'
borderRadius='0 4px 4px 0'
border='1px solid'
borderColor='lightGray'
>
<Icon icon='X' />
</Box>
);
}
return null;
};
export function ImageInput(props: ImageInputProps): ReactElement {
const { id, label, caption, placeholder } = props;
const { id, label, caption } = props;
const { uploadDefault, canUpload, uploading } = useStorage();
const [field, meta, { setValue, setError }] = useField(id);
const ref = useRef<HTMLInputElement | null>(null);
const onImageUpload = useCallback(async () => {
@ -43,10 +130,14 @@ export function ImageInput(props: ImageInputProps): ReactElement {
}
}, [ref.current, uploadDefault, canUpload, setValue]);
const onClick = useCallback(() => {
const clickUploadButton = useCallback(() => {
ref.current?.click();
}, [ref]);
const clearEvt = useCallback(() => {
setValue('');
}, []);
return (
<Box display="flex" flexDirection="column" {...props}>
<Label htmlFor={id}>{label}</Label>
@ -55,25 +146,25 @@ export function ImageInput(props: ImageInputProps): ReactElement {
{caption}
</Label>
) : null}
<Row mt="2" alignItems="flex-end">
<Input
type={'text'}
hasError={meta.touched && meta.error !== undefined}
placeholder={placeholder}
{...field}
/>
<Row mt="2" alignItems="flex-end" position='relative' width='100%'>
{prompt(field, uploading, meta, clickUploadButton)}
{clearButton(field, uploading, clearEvt)}
{uploadingStatus(uploading, meta)}
{errorRetry(meta, uploading, clickUploadButton)}
<Box background='white' borderRadius={2} width='100%'>
<Input
width='100%'
type={'text'}
hasError={meta.touched && meta.error !== undefined}
{...field}
/>
</Box>
{canUpload && (
<>
<Button
type="button"
ml={1}
border={1}
borderColor="lightGray"
onClick={onClick}
flexShrink={0}
>
{uploading ? 'Uploading' : 'Upload'}
</Button>
display='none'
onClick={clickUploadButton}
/>
<BaseInput
style={{ display: 'none' }}
type="file"
@ -85,9 +176,6 @@ export function ImageInput(props: ImageInputProps): ReactElement {
</>
)}
</Row>
<ErrorLabel mt="2" hasError={Boolean(meta.touched && meta.error)}>
{meta.error}
</ErrorLabel>
</Box>
);
}

View File

@ -1,11 +1,16 @@
import React, { useMemo, useRef, useCallback, useEffect, useState } from 'react';
import React, {
useMemo,
useRef,
useCallback,
useEffect,
useState
} from 'react';
import { useLocation, useHistory } from 'react-router-dom';
import * as ob from 'urbit-ob';
import Mousetrap from 'mousetrap';
import { omit } from 'lodash';
import { Box, Row, Text } from '@tlon/indigo-react';
import { Associations, Contacts, Groups, Invites } from '@urbit/api';
import makeIndex from '~/logic/lib/omnibox';
import OmniboxInput from './OmniboxInput';
import OmniboxResult from './OmniboxResult';
@ -13,10 +18,9 @@ import { deSig } from '~/logic/lib/util';
import { withLocalState } from '~/logic/state/local';
import defaultApps from '~/logic/lib/default-apps';
import {useOutsideClick} from '~/logic/lib/useOutsideClick';
import {Portal} from '../Portal';
import useSettingsState, {SettingsState} from '~/logic/state/settings';
import { Tile } from '~/types';
import { useOutsideClick } from '~/logic/lib/useOutsideClick';
import { Portal } from '../Portal';
import useSettingsState, { SettingsState } from '~/logic/state/settings';
import useContactState from '~/logic/state/contact';
import useGroupState from '~/logic/state/group';
import useHarkState from '~/logic/state/hark';
@ -30,14 +34,21 @@ interface OmniboxProps {
notifications: number;
}
const SEARCHED_CATEGORIES = ['commands', 'ships', 'other', 'groups', 'subscriptions', 'apps'];
const SEARCHED_CATEGORIES = [
'commands',
'ships',
'other',
'groups',
'subscriptions',
'apps'
];
const settingsSel = (s: SettingsState) => s.leap;
export function Omnibox(props: OmniboxProps) {
export function Omnibox(props: OmniboxProps): ReactElement {
const location = useLocation();
const history = useHistory();
const leapConfig = useSettingsState(settingsSel);
const omniboxRef = useRef<HTMLDivElement | null>(null)
const omniboxRef = useRef<HTMLDivElement | null>(null);
const inputRef = useRef<HTMLInputElement | null>(null);
const [query, setQuery] = useState('');
@ -46,21 +57,24 @@ export function Omnibox(props: OmniboxProps) {
const notifications = useHarkState(state => state.notifications);
const invites = useInviteState(state => state.invites);
const tiles = useLaunchState(state => state.tiles);
const [leapCursor, setLeapCursor] = useState('pointer');
const contacts = useMemo(() => {
const maybeShip = `~${deSig(query)}`;
return ob.isValidPatp(maybeShip)
? { ...contactState, [maybeShip]: {} }
: contactState;
const selflessContactState = omit(contactState, `~${window.ship}`);
return ob.isValidPatp(maybeShip) && maybeShip !== `~${window.ship}`
? { ...selflessContactState, [maybeShip]: {} }
: selflessContactState;
}, [contactState, query]);
const groups = useGroupState(state => state.groups);
const associations = useMetadataState(state => state.associations);
const selectedGroup = useMemo(
() => location.pathname.startsWith('/~landscape/ship/')
? '/' + location.pathname.split('/').slice(2,5).join('/')
: null,
const selectedGroup = useMemo(
() =>
location.pathname.startsWith('/~landscape/ship/')
? '/' + location.pathname.split('/').slice(2, 5).join('/')
: null,
[location.pathname]
);
@ -71,16 +85,9 @@ export function Omnibox(props: OmniboxProps) {
tiles,
selectedGroup,
groups,
leapConfig,
leapConfig
);
}, [
selectedGroup,
leapConfig,
contacts,
associations,
groups,
tiles
]);
}, [selectedGroup, leapConfig, contacts, associations, groups, tiles]);
const onOutsideClick = useCallback(() => {
props.show && props.toggle();
@ -90,7 +97,7 @@ export function Omnibox(props: OmniboxProps) {
// handle omnibox show
useEffect(() => {
if(!props.show) {
if (!props.show) {
return;
}
Mousetrap.bind('escape', props.toggle);
@ -104,29 +111,37 @@ export function Omnibox(props: OmniboxProps) {
}, [props.show]);
const initialResults = useMemo(() => {
return new Map(SEARCHED_CATEGORIES.map((category) => {
if (category === 'other') {
return ['other', index.get('other').filter(({ app }) => app !== 'tutorial')];
}
return [category, []];
}));
return new Map(
SEARCHED_CATEGORIES.map((category) => {
if (category === 'other') {
return [
'other',
index.get('other').filter(({ app }) => app !== 'tutorial')
];
}
return [category, []];
})
);
}, [index]);
const results = useMemo(() => {
if(query.length <= 1) {
if (query.length <= 1) {
return initialResults;
}
const q = query.toLowerCase();
const resultsMap = new Map();
SEARCHED_CATEGORIES.map((category) => {
const categoryIndex = index.get(category);
resultsMap.set(category,
resultsMap.set(
category,
categoryIndex.filter((result) => {
return (
result.title.toLowerCase().includes(q) ||
result.link.toLowerCase().includes(q) ||
result.app.toLowerCase().includes(q) ||
(result.host !== null ? result.host.toLowerCase().includes(q) : false)
(result.host !== null
? result.host.toLowerCase().includes(q)
: false)
);
})
);
@ -134,21 +149,26 @@ export function Omnibox(props: OmniboxProps) {
return resultsMap;
}, [query, index]);
const navigate = useCallback((app: string, link: string) => {
props.toggle();
if (defaultApps.includes(app.toLowerCase())
|| app === 'profile'
|| app === 'messages'
|| app === 'tutorial'
|| app === 'Links'
|| app === 'Terminal'
|| app === 'home'
|| app === 'inbox') {
history.push(link);
} else {
window.location.href = link;
}
}, [history, props.toggle]);
const navigate = useCallback(
(app: string, link: string) => {
props.toggle();
if (
defaultApps.includes(app.toLowerCase()) ||
app === 'profile' ||
app === 'messages' ||
app === 'tutorial' ||
app === 'Links' ||
app === 'Terminal' ||
app === 'home' ||
app === 'inbox'
) {
history.push(link);
} else {
window.location.href = link;
}
},
[history, props.toggle]
);
const setPreviousSelected = useCallback(() => {
const flattenedResults = Array.from(results.values()).flat();
@ -193,50 +213,59 @@ export function Omnibox(props: OmniboxProps) {
}
}, [selected, results]);
const control = useCallback((evt) => {
if (evt.key === 'Escape') {
if (query.length > 0) {
setQuery('');
return;
} else if (props.show) {
props.toggle();
return;
const setSelection = (app, link) => {
setLeapCursor('pointer');
setSelected([app, link]);
};
const control = useCallback(
(evt) => {
if (evt.key === 'Escape') {
if (query.length > 0) {
setQuery('');
return;
} else if (props.show) {
props.toggle();
return;
}
}
}
if (
evt.key === 'ArrowUp' ||
(evt.shiftKey && evt.key === 'Tab')) {
if (evt.key === 'ArrowUp' || (evt.shiftKey && evt.key === 'Tab')) {
evt.preventDefault();
setPreviousSelected();
return;
}
if (evt.key === 'ArrowDown' || evt.key === 'Tab') {
evt.preventDefault();
setNextSelected();
return;
}
if (evt.key === 'Enter') {
evt.preventDefault();
if (selected.length) {
navigate(selected[0], selected[1]);
} else if (Array.from(results.values()).flat().length === 0) {
setPreviousSelected();
setLeapCursor('none');
return;
} else {
navigate(
Array.from(results.values()).flat()[0].app,
Array.from(results.values()).flat()[0].link);
}
}
}, [
props.toggle,
selected,
navigate,
query,
props.show,
results,
setPreviousSelected,
setNextSelected
]);
if (evt.key === 'ArrowDown' || evt.key === 'Tab') {
evt.preventDefault();
setNextSelected();
setLeapCursor('none');
return;
}
if (evt.key === 'Enter') {
evt.preventDefault();
if (selected.length) {
navigate(selected[0], selected[1]);
} else if (Array.from(results.values()).flat().length === 0) {
return;
} else {
navigate(
Array.from(results.values()).flat()[0].app,
Array.from(results.values()).flat()[0].link
);
}
}
},
[
props.toggle,
selected,
navigate,
query,
props.show,
results,
setPreviousSelected,
setNextSelected
]
);
useEffect(() => {
const flattenedResultLinks = Array.from(results.values())
@ -252,88 +281,106 @@ export function Omnibox(props: OmniboxProps) {
}, []);
// Sort Omnibox results alphabetically
const sortResults = (a: Record<'title', string>, b: Record<'title', string>) => {
const sortResults = (
a: Record<'title', string>,
b: Record<'title', string>
) => {
// Do not sort unless searching (preserves order of menu actions)
if (query === '') { return 0 };
if (a.title < b.title) { return -1 };
if (a.title > b.title) { return 1 };
if (query === '') {
return 0;
}
if (a.title < b.title) {
return -1;
}
if (a.title > b.title) {
return 1;
}
return 0;
}
};
const renderResults = useCallback(() => {
return <Box
maxHeight={['200px', '400px']}
overflowY="auto"
overflowX="hidden"
borderBottomLeftRadius='2'
borderBottomRightRadius='2'
>
{SEARCHED_CATEGORIES
.map(category => Object({ category, categoryResults: results.get(category) }))
.filter(category => category.categoryResults.length > 0)
.map(({ category, categoryResults }, i) => {
const categoryTitle = (category === 'other')
? null : <Row pl='2' height='5' alignItems='center' bg='washedGray'><Text gray bold>{category.charAt(0).toUpperCase() + category.slice(1)}</Text></Row>;
const sel = selected?.length ? selected[1] : '';
return (<Box key={i} width='max(50vw, 300px)' maxWidth='600px'>
{categoryTitle}
{categoryResults
.sort(sortResults)
.map((result, i2) => (
<OmniboxResult
key={i2}
icon={result.app}
text={result.title}
subtext={result.host}
link={result.link}
navigate={() => navigate(result.app, result.link)}
selected={sel}
/>
))}
</Box>
);
})
}
</Box>;
return (
<Box
maxHeight={['200px', '400px']}
overflowY='auto'
overflowX='hidden'
borderBottomLeftRadius='2'
borderBottomRightRadius='2'
>
{SEARCHED_CATEGORIES.map(category =>
Object({ category, categoryResults: results.get(category) })
)
.filter(category => category.categoryResults.length > 0)
.map(({ category, categoryResults }, i) => {
const categoryTitle =
category === 'other' ? null : (
<Row pl='2' height='5' alignItems='center' bg='washedGray'>
<Text gray bold>
{category.charAt(0).toUpperCase() + category.slice(1)}
</Text>
</Row>
);
const sel = selected?.length ? selected[1] : '';
return (
<Box key={i} width='max(50vw, 300px)' maxWidth='600px'>
{categoryTitle}
{categoryResults.sort(sortResults).map((result, i2) => (
<OmniboxResult
key={i2}
icon={result.app}
text={result.title}
subtext={result.host}
link={result.link}
cursor={leapCursor}
navigate={() => navigate(result.app, result.link)}
setSelection={() => setSelection(result.app, result.link)}
selected={sel}
/>
))}
</Box>
);
})}
</Box>
);
}, [results, navigate, selected, contactState, notifications, invites]);
return (
<Portal>
<Box
backgroundColor='scales.black30'
width='100%'
height='100%'
position='absolute'
top='0'
right='0'
zIndex={11}
display={props.show ? 'block' : 'none'}
>
<Row justifyContent='center'>
<Box
mt={['10vh', '20vh']}
width='max(50vw, 300px)'
maxWidth='600px'
borderRadius='2'
backgroundColor='white'
<Box
backgroundColor='scales.black30'
width='100%'
height='100%'
position='absolute'
top='0'
right='0'
zIndex={11}
display={props.show ? 'block' : 'none'}
>
<Row justifyContent='center'>
<Box
mt={['10vh', '20vh']}
width='max(50vw, 300px)'
maxWidth='600px'
borderRadius='2'
backgroundColor='white'
ref={(el) => {
omniboxRef.current = el;
}}
>
<OmniboxInput
ref={(el) => {
omniboxRef.current = el;
}}
>
<OmniboxInput
ref={(el) => {
inputRef.current = el;
}}
control={e => control(e)}
search={search}
query={query}
/>
{renderResults()}
</Box>
</Row>
</Box>
</Portal>
);
}
inputRef.current = el;
}}
control={e => control(e)}
search={search}
query={query}
/>
{renderResults()}
</Box>
</Row>
</Box>
</Portal>
);
}
export default withLocalState(Omnibox, ['toggleOmnibox', 'omniboxShown']);

View File

@ -1,4 +1,3 @@
import React, { Component } from 'react';
import { BaseInput } from '@tlon/indigo-react';
@ -6,33 +5,31 @@ export class OmniboxInput extends Component {
render() {
const { props } = this;
return (
<BaseInput
ref={(el) => {
this.input = el;
<BaseInput
ref={(el) => {
this.input = el;
if (el && document.activeElement.isSameNode(el)) {
el.blur();
el.focus();
}
}
}
width='100%'
p='2'
backgroundColor='white'
color='black'
border='1px solid transparent'
borderRadius='2'
maxWidth='calc(600px - 1.15rem)'
fontSize='1'
style={{ boxSizing: 'border-box' }}
placeholder='Search...'
onKeyDown={props.control}
onChange={props.search}
spellCheck={false}
value={props.query}
/>
}}
width='100%'
p='2'
backgroundColor='white'
color='black'
border='1px solid transparent'
borderRadius='2'
maxWidth='calc(600px - 1.15rem)'
fontSize='1'
style={{ boxSizing: 'border-box' }}
placeholder='Search...'
onKeyDown={props.control}
onChange={props.search}
spellCheck={false}
value={props.query}
/>
);
}
}
export default OmniboxInput;

View File

@ -21,52 +21,143 @@ export class OmniboxResult extends Component {
componentDidUpdate(prevProps) {
const { props, state } = this;
if (prevProps &&
if (
prevProps &&
!state.hovered &&
prevProps.selected !== props.selected &&
props.selected === props.link
) {
this.result.current.scrollIntoView({ block: 'nearest' });
}
) {
this.result.current.scrollIntoView({ block: 'nearest' });
}
}
getIcon(icon, selected, link, invites, notifications, text, color) {
const iconFill = (this.state.hovered || (selected === link)) ? 'white' : 'black';
const bulletFill = (this.state.hovered || (selected === link)) ? 'white' : 'blue';
const iconFill =
this.state.hovered || selected === link ? 'white' : 'black';
const bulletFill =
this.state.hovered || selected === link ? 'white' : 'blue';
const inviteCount = [].concat(...Object.values(invites).map(obj => Object.values(obj)));
const inviteCount = [].concat(
...Object.values(invites).map((obj) => Object.values(obj))
);
let graphic = <div />;
if (defaultApps.includes(icon.toLowerCase())
|| icon.toLowerCase() === 'links'
|| icon.toLowerCase() === 'terminal')
{
icon = (icon === 'Link') ? 'Collection' :
(icon === 'Terminal') ? 'Dojo' : icon;
graphic = <Icon display="inline-block" verticalAlign="middle" icon={icon} mr='2' size='18px' color={iconFill} />;
if (
defaultApps.includes(icon.toLowerCase()) ||
icon.toLowerCase() === 'links' ||
icon.toLowerCase() === 'terminal'
) {
icon =
icon === 'Link' ? 'Collection' : icon === 'Terminal' ? 'Dojo' : icon;
graphic = (
<Icon
display='inline-block'
verticalAlign='middle'
icon={icon}
mr='2'
size='18px'
color={iconFill}
/>
);
} else if (icon === 'inbox') {
graphic = <Box display='flex' verticalAlign='middle' position="relative">
<Icon display='inline-block' verticalAlign='middle' icon='Inbox' mr='2' size='18px' color={iconFill} />
{(notifications > 0 || inviteCount.length > 0) && (
<Icon display='inline-block' icon='Bullet' style={{ position: 'absolute', top: -5, left: 5 }} color={bulletFill} />
)}
</Box>;
graphic = (
<Box display='flex' verticalAlign='middle' position='relative'>
<Icon
display='inline-block'
verticalAlign='middle'
icon='Inbox'
mr='2'
size='18px'
color={iconFill}
/>
{(notifications > 0 || inviteCount.length > 0) && (
<Icon
display='inline-block'
icon='Bullet'
style={{ position: 'absolute', top: -5, left: 5 }}
color={bulletFill}
/>
)}
</Box>
);
} else if (icon === 'logout') {
graphic = <Icon display="inline-block" verticalAlign="middle" icon='SignOut' mr='2' size='18px' color={iconFill} />;
graphic = (
<Icon
display='inline-block'
verticalAlign='middle'
icon='SignOut'
mr='2'
size='18px'
color={iconFill}
/>
);
} else if (icon === 'profile') {
text = text.startsWith('Profile') ? window.ship : text;
graphic = <Sigil color={color} classes='dib flex-shrink-0 v-mid mr2' ship={text} size={18} icon padding={2} />;
graphic = (
<Sigil
color={color}
classes='dib flex-shrink-0 v-mid mr2'
ship={text}
size={18}
icon
padding={2}
/>
);
} else if (icon === 'home') {
graphic = <Icon display='inline-block' verticalAlign='middle' icon='Home' mr='2' size='18px' color={iconFill} />;
graphic = (
<Icon
display='inline-block'
verticalAlign='middle'
icon='Home'
mr='2'
size='18px'
color={iconFill}
/>
);
} else if (icon === 'notifications') {
graphic = <Icon display='inline-block' verticalAlign='middle' icon='Inbox' mr='2' size='18px' color={iconFill} />;
graphic = (
<Icon
display='inline-block'
verticalAlign='middle'
icon='Inbox'
mr='2'
size='18px'
color={iconFill}
/>
);
} else if (icon === 'messages') {
graphic = <Icon display='inline-block' verticalAlign='middle' icon='Users' mr='2' size='18px' color={iconFill} />;
graphic = (
<Icon
display='inline-block'
verticalAlign='middle'
icon='Users'
mr='2'
size='18px'
color={iconFill}
/>
);
} else if (icon === 'tutorial') {
graphic = <Icon display='inline-block' verticalAlign='middle' icon='Tutorial' mr='2' size='18px' color={iconFill} />;
}
else {
graphic = <Icon display='inline-block' icon='NullIcon' verticalAlign="middle" mr='2' size="16px" color={iconFill} />;
graphic = (
<Icon
display='inline-block'
verticalAlign='middle'
icon='Tutorial'
mr='2'
size='18px'
color={iconFill}
/>
);
} else {
graphic = (
<Icon
display='inline-block'
icon='NullIcon'
verticalAlign='middle'
mr='2'
size='16px'
color={iconFill}
/>
);
}
return graphic;
@ -77,53 +168,81 @@ export class OmniboxResult extends Component {
}
render() {
const { icon, text, subtext, link, navigate, selected, invites, notificationsCount, contacts } = this.props;
const {
icon,
text,
subtext,
link,
cursor,
navigate,
selected,
invites,
notificationsCount,
contacts,
setSelection
} = this.props;
const color = contacts?.[text] ? `#${uxToHex(contacts[text].color)}` : "#000000";
const graphic = this.getIcon(icon, selected, link, invites, notificationsCount, text, color);
const color = contacts?.[text]
? `#${uxToHex(contacts[text].color)}`
: '#000000';
const graphic = this.getIcon(
icon,
selected,
link,
invites,
notificationsCount,
text,
color
);
return (
<Row
py='2'
px='2'
cursor='pointer'
onMouseEnter={() => this.setHover(true)}
onMouseLeave={() => this.setHover(false)}
backgroundColor={
this.state.hovered || selected === link ? 'blue' : 'white'
}
onClick={navigate}
width="100%"
justifyContent="space-between"
ref={this.result}
py='2'
px='2'
cursor={cursor}
onMouseMove={() => setSelection()}
onMouseLeave={() => this.setHover(false)}
backgroundColor={
this.state.hovered || selected === link ? 'blue' : 'white'
}
onClick={navigate}
width='100%'
justifyContent='space-between'
ref={this.result}
>
<Box display="flex" verticalAlign="middle" maxWidth="60%" flexShrink={0}>
{graphic}
<Text
mono={(icon == 'profile' && text.startsWith('~'))}
color={this.state.hovered || selected === link ? 'white' : 'black'}
display='inline-block'
verticalAlign='middle'
width='100%'
overflow='hidden'
textOverflow='ellipsis'
whiteSpace='pre'
mr='1'
<Box
display='flex'
verticalAlign='middle'
maxWidth='60%'
flexShrink={0}
>
{text.startsWith("~") ? cite(text) : text}
</Text>
{graphic}
<Text
mono={icon == 'profile' && text.startsWith('~')}
color={this.state.hovered || selected === link ? 'white' : 'black'}
display='inline-block'
verticalAlign='middle'
width='100%'
overflow='hidden'
textOverflow='ellipsis'
whiteSpace='pre'
mr='1'
>
{text.startsWith('~') ? cite(text) : text}
</Text>
</Box>
<Text pr='2'
display="inline-block"
verticalAlign="middle"
color={this.state.hovered || selected === link ? 'white' : 'black'}
width='100%'
minWidth={0}
textOverflow="ellipsis"
whiteSpace="pre"
overflow="hidden"
maxWidth="40%"
textAlign='right'
<Text
pr='2'
display='inline-block'
verticalAlign='middle'
color={this.state.hovered || selected === link ? 'white' : 'black'}
width='100%'
minWidth={0}
textOverflow='ellipsis'
whiteSpace='pre'
overflow='hidden'
maxWidth='40%'
textAlign='right'
>
{subtext}
</Text>
@ -136,4 +255,4 @@ export default withState(OmniboxResult, [
[useInviteState],
[useHarkState, ['notificationsCount']],
[useContactState]
]);
]);

View File

@ -18,7 +18,11 @@ export function useGraphModule(
}
const notifications = graphUnreads?.[s]?.['/']?.notifications;
if ( notifications > 0 ) {
if (
notifications &&
((typeof notifications === 'number' && notifications > 0)
|| notifications.length)
) {
return 'notification';
}