Merge branch 'mp/leap-tweaks' (#3420)

* origin/mp/leap-tweaks:
  leap: better compensate for long titles
  leap: show group's name, not the host ship
  leap: add "other" actions, prepopulate results
  leap: move leap to its own subfolder

Signed-off-by: Matilde Park <matilde.park@sunshinegardens.org>
This commit is contained in:
Matilde Park 2020-09-02 15:48:02 -04:00
commit 4c8c2eaa6d
6 changed files with 195 additions and 117 deletions

View File

@ -1,10 +1,12 @@
import defaultApps from './default-apps';
import { cite } from '~/logic/lib/util';
const indexes = new Map([
['commands', []],
['subscriptions', []],
['groups', []],
['apps', []]
['apps', []],
['other', []]
]);
// result schematic
@ -41,8 +43,6 @@ const commandIndex = function () {
}
});
commands.push(result('Profile', '/~profile', 'profile', null));
return commands;
};
@ -54,6 +54,9 @@ const appIndex = function (apps) {
.filter((e) => {
return apps[e]?.type?.basic;
})
.sort((a,b) => {
return a.localeCompare(b);
})
.map((e) => {
const obj = result(
apps[e].type.basic.title,
@ -70,6 +73,14 @@ const appIndex = function (apps) {
return applications;
};
const otherIndex = function() {
const other = [];
other.push(result('Profile and Settings', '/~profile/identity', 'profile', null));
other.push(result('Log Out', '/~/logout', 'logout', null));
return other;
};
export default function index(associations, apps) {
// all metadata from all apps is indexed
// into subscriptions and groups
@ -99,7 +110,7 @@ export default function index(associations, apps) {
title,
`/~${app}${each['app-path']}`,
app.charAt(0).toUpperCase() + app.slice(1),
shipStart.slice(0, shipStart.indexOf('/'))
cite(shipStart.slice(0, shipStart.indexOf('/')))
);
groups.push(obj);
} else {
@ -107,7 +118,7 @@ export default function index(associations, apps) {
title,
`/~${each['app-name']}/join${each['app-path']}`,
app.charAt(0).toUpperCase() + app.slice(1),
shipStart.slice(0, shipStart.indexOf('/'))
(associations?.contacts?.[each['group-path']]?.metadata?.title || null)
);
subscriptions.push(obj);
}
@ -118,6 +129,7 @@ export default function index(associations, apps) {
indexes.set('subscriptions', subscriptions);
indexes.set('groups', groups);
indexes.set('apps', appIndex(apps));
indexes.set('other', otherIndex());
return indexes;
};

View File

@ -1,7 +1,7 @@
import { hot } from 'react-hot-loader/root';
import 'react-hot-loader';
import * as React from 'react';
import { BrowserRouter as Router, Route, withRouter, Switch } from 'react-router-dom';
import { BrowserRouter as Router, withRouter } from 'react-router-dom';
import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
import { sigil as sigiljs, stringRenderer } from 'urbit-sigil-js';
import Helmet from 'react-helmet';
@ -16,8 +16,7 @@ import dark from './themes/old-dark';
import { Content } from './components/Content';
import StatusBar from './components/StatusBar';
import Omnibox from './components/Omnibox';
import ErrorComponent from './components/Error';
import Omnibox from './components/leap/Omnibox';
import GlobalStore from '~/logic/store/store';
import GlobalSubscription from '~/logic/subscription/global';
@ -36,7 +35,7 @@ const Root = styled.div`
background-size: cover;
` : p.background?.type === 'color' ? `
background-color: ${p.background.color};
` : ``
` : ''
}
display: flex;
flex-flow: column nowrap;
@ -135,7 +134,8 @@ class App extends React.Component {
ship={this.ship}
api={this.api}
subscription={this.subscription}
{...state} />
{...state}
/>
</Router>
</Root>
</ThemeProvider>
@ -143,6 +143,5 @@ class App extends React.Component {
}
}
export default process.env.NODE_ENV === 'production' ? App : hot(App);

View File

@ -1,96 +0,0 @@
import React, { Component } from 'react';
import { Row, Icon, Text } from '@tlon/indigo-react';
import defaultApps from '~/logic/lib/default-apps';
export class OmniboxResult extends Component {
constructor(props) {
super(props);
this.state = {
isSelected: false,
hovered: false
};
this.setHover = this.setHover.bind(this);
this.result = React.createRef();
}
componentDidUpdate(prevProps) {
const { props, state } = this;
if (prevProps &&
!state.hovered &&
prevProps.selected !== props.selected &&
props.selected === props.link
) {
this.result.current.scrollIntoView({ block: 'nearest' });
}
}
setHover(boolean) {
this.setState({ hovered: boolean });
}
render() {
const { icon, text, subtext, link, navigate, selected, dark } = this.props;
let invertGraphic = {};
if (icon.toLowerCase() !== 'dojo') {
invertGraphic = (!dark && this.state.hovered) ||
selected === link ||
(dark && !(this.state.hovered || selected === link))
? { filter: 'invert(1)', paddingTop: 2 }
: { filter: 'invert(0)', paddingTop: 2 };
} else {
invertGraphic =
(!dark && this.state.hovered) ||
selected === link ||
(dark && !(this.state.hovered || selected === link))
? { filter: 'invert(0)', paddingTop: 2 }
: { filter: 'invert(1)', paddingTop: 2 };
}
let graphic = <div />;
if (defaultApps.includes(icon.toLowerCase()) || icon.toLowerCase() === 'links') {
graphic = <img className="mr2 v-mid" height="12" width="12" src={`/~landscape/img/${icon.toLowerCase()}.png`} style={invertGraphic} />;
} else {
graphic = <Icon verticalAlign="middle" mr={2} size="12px" />;
}
return (
<Row
py='2'
px='2'
display='flex'
flexDirection='row'
style={{ cursor: 'pointer' }}
onMouseEnter={() => this.setHover(true)}
onMouseLeave={() => this.setHover(false)}
backgroundColor={
this.state.hovered || selected === link ? 'blue' : 'white'
}
onClick={navigate}
width="100%"
ref={this.result}
>
{this.state.hovered || selected === link ? (
<>
{graphic}
<Text color='white' mr='1' style={{ 'flex-shrink': 0 }}>
{text}
</Text>
<Text pr='2' color='white' width='100%' textAlign='right'>
{subtext}
</Text>
</>
) : (
<>
{graphic}
<Text mr='1' style={{ 'flex-shrink': 0 }}>{text}</Text>
<Text pr='2' gray width='100%' textAlign='right'>
{subtext}
</Text>
</>
)}
</Row>
);
}
}
export default OmniboxResult;

View File

@ -6,8 +6,6 @@ import Mousetrap from 'mousetrap';
import OmniboxInput from './OmniboxInput';
import OmniboxResult from './OmniboxResult';
import { cite } from '~/logic/lib/util';
export class Omnibox extends Component {
constructor(props) {
super(props);
@ -26,11 +24,15 @@ export class Omnibox extends Component {
this.renderResults = this.renderResults.bind(this);
}
componentDidUpdate(prevProps) {
componentDidUpdate(prevProps, prevState) {
if (prevProps !== this.props) {
this.setState({ index: index(this.props.associations, this.props.apps.tiles) });
}
if (prevProps && (prevProps.apps !== this.props.apps) && (this.state.query === '')) {
this.setState({ results: this.initialResults() });
}
if (prevProps && this.props.show && prevProps.show !== this.props.show) {
Mousetrap.bind('escape', () => this.props.api.local.setOmnibox());
document.addEventListener('mousedown', this.handleClickOutside);
@ -48,7 +50,7 @@ export class Omnibox extends Component {
}
getSearchedCategories() {
return ['apps', 'commands', 'groups', 'subscriptions'];
return ['apps', 'commands', 'groups', 'subscriptions', 'other'];
}
control(evt) {
@ -91,7 +93,18 @@ export class Omnibox extends Component {
}
initialResults() {
return new Map(this.getSearchedCategories().map(category => [category, []]));
return new Map(this.getSearchedCategories().map((category) => {
if (!this.state) {
return [category, []];
}
if (category === 'apps') {
return ['apps', this.state.index.get('apps')];
}
if (category === 'other') {
return ['other', this.state.index.get('other')];
}
return [category, []];
}));
}
navigate(link) {
@ -202,23 +215,26 @@ export class Omnibox extends Component {
{this.getSearchedCategories()
.map(category => Object({ category, categoryResults: state.results.get(category) }))
.filter(category => category.categoryResults.length > 0)
.map(({ category, categoryResults }, i) => (
<Box key={i} width='max(50vw, 300px)' maxWidth='600px'>
.map(({ category, categoryResults }, i) => {
const categoryTitle = (category === 'other')
? null : <Text gray ml={2}>{category.charAt(0).toUpperCase() + category.slice(1)}</Text>;
return (<Box key={i} width='max(50vw, 300px)' maxWidth='600px'>
<Rule borderTopWidth="0.5px" color="washedGray" />
<Text gray ml={2}>{category.charAt(0).toUpperCase() + category.slice(1)}</Text>
{categoryTitle}
{categoryResults.map((result, i2) => (
<OmniboxResult
key={i2}
icon={result.app}
text={result.title}
subtext={cite(result.host)}
subtext={result.host}
link={result.link}
navigate={() => this.navigate(result.link)}
selected={this.state.selected}
dark={props.dark} />
))}
</Box>
))
);
})
}
</Box>;
}

View File

@ -0,0 +1,147 @@
import React, { Component } from 'react';
import { Row, Icon, Text } from '@tlon/indigo-react';
import defaultApps from '~/logic/lib/default-apps';
import Sigil from '~/logic/lib/sigil';
export class OmniboxResult extends Component {
constructor(props) {
super(props);
this.state = {
isSelected: false,
hovered: false
};
this.setHover = this.setHover.bind(this);
this.result = React.createRef();
}
componentDidUpdate(prevProps) {
const { props, state } = this;
if (prevProps &&
!state.hovered &&
prevProps.selected !== props.selected &&
props.selected === props.link
) {
this.result.current.scrollIntoView({ block: 'nearest' });
}
}
getIcon(icon, dark, selected, link) {
// graphicStyle is only necessary for pngs
//
//TODO can be removed after indigo-react 1.2
//which includes icons for apps
let graphicStyle = {};
if (icon.toLowerCase() !== 'dojo') {
graphicStyle = (!dark && this.state.hovered) ||
selected === link ||
(dark && !(this.state.hovered || selected === link))
? { filter: 'invert(1)' }
: { filter: 'invert(0)' };
} else {
graphicStyle =
(!dark && this.state.hovered) ||
selected === link ||
(dark && !(this.state.hovered || selected === link))
? { filter: 'invert(0)' }
: { filter: 'invert(1)' };
}
const iconFill = this.state.hovered || selected === link ? 'white' : 'black';
const sigilFill = this.state.hovered || selected === link ? '#3a8ff7' : '#ffffff';
let graphic = <div />;
if (defaultApps.includes(icon.toLowerCase()) || icon.toLowerCase() === 'links') {
graphic =
<img className="mr2 v-mid dib" height="16"
width="16" src={`/~landscape/img/${icon.toLowerCase()}.png`}
style={graphicStyle}
/>;
} else if (icon === 'logout') {
graphic = <Icon display="inline-block" verticalAlign="middle" icon='ArrowWest' mr='2' size='16px' fill={iconFill} />;
} else if (icon === 'profile') {
graphic = <Sigil color={sigilFill} classes='dib v-mid mr2' ship={window.ship} size={16} />;
} else {
graphic = <Icon verticalAlign="middle" mr='2' size="16px" fill={iconFill} />;
}
return graphic;
}
setHover(boolean) {
this.setState({ hovered: boolean });
}
render() {
const { icon, text, subtext, link, navigate, selected, dark } = this.props;
const graphic = this.getIcon(icon, dark, selected, link);
return (
<Row
py='2'
px='2'
display='flex'
flexDirection='row'
style={{ cursor: 'pointer' }}
onMouseEnter={() => this.setHover(true)}
onMouseLeave={() => this.setHover(false)}
backgroundColor={
this.state.hovered || selected === link ? 'blue' : 'white'
}
onClick={navigate}
width="100%"
ref={this.result}
>
{this.state.hovered || selected === link ? (
<>
{graphic}
<Text
display="inline-block"
verticalAlign="middle"
color='white'
maxWidth="60%"
style={{ 'flex-shrink': 0 }}
mr='1'>
{text}
</Text>
<Text pr='2'
display="inline-block"
verticalAlign="middle"
color='white'
width='100%'
textAlign='right'
>
{subtext}
</Text>
</>
) : (
<>
{graphic}
<Text
mr='1'
display="inline-block"
verticalAlign="middle"
maxWidth="60%"
style={{ 'flex-shrink': 0 }}
>
{text}
</Text>
<Text
pr='2'
display="inline-block"
verticalAlign="middle"
gray
width='100%'
textAlign='right'
>
{subtext}
</Text>
</>
)}
</Row>
);
}
}
export default OmniboxResult;