Merge remote-tracking branch 'origin/release/next-userspace' into release/2021-5-27

This commit is contained in:
Liam Fitzgerald 2021-06-02 10:12:05 +10:00
commit e510cbeb03
No known key found for this signature in database
GPG Key ID: D390E12C61D1CFFB
17 changed files with 170 additions and 302 deletions

View File

@ -1,4 +1,4 @@
FROM jaredtobin/janeway:v0.15.0
FROM jaredtobin/janeway:v0.15.1
COPY entrypoint.sh /entrypoint.sh
EXPOSE 22/tcp
ENTRYPOINT ["/entrypoint.sh"]

View File

@ -304,8 +304,10 @@
unversioned
=/ =resource
(de-path:resource t.t.path)
=/ requested=@ud
(slav %ud i.t.t.t.t.t.path)
=/ =mark
(append-version:ver (slav %ud i.t.t.t.t.t.path))
(append-version:ver (min requested version.config))
?. (supported:ver mark)
:_ this
(fact-init-kick:io version+!>(min-version.config))
@ -480,7 +482,7 @@
%+ turn ~(tap by paths)
|= [fact-ver=@ud paths=(set path)]
=/ =mark
(append-version:ver fact-ver)
(append-version:ver (min version.config fact-ver))
(fact:io (convert-from:ver mark q.cage) ~(tap in paths))
:: TODO: deprecate
++ unversioned

View File

@ -42,7 +42,7 @@ if(urbitrc.URL) {
...devServer,
index: '',
proxy: {
'/~btc/js/bundle/index.js': {
'/~btc/js/bundle/index.*.js': {
target: 'http://localhost:9000',
pathRewrite: (req, path) => {
return '/index.js'

View File

@ -90,8 +90,6 @@ export default class BridgeInvoice extends Component {
inputBorder = 'red';
}
console.log('bridge invoice', error);
return (
<>
{ this.props.state.broadcastSuccess ?

View File

@ -30,7 +30,6 @@ export class Root extends Component {
this.ship = window.ship;
this.state = store.state;
store.setStateHandler(this.setState.bind(this));
console.log('state', this.state);
}
componentDidMount(){

View File

@ -6,7 +6,6 @@ export class UpdateReducer {
if (!json) {
return;
}
console.log('reduce', json);
if (json.providerStatus) {
this.reduceProviderStatus(json.providerStatus, state);
}

View File

@ -53,16 +53,16 @@ const appIndex = function (apps) {
const applications = [];
Object.keys(apps)
.filter((e) => {
return apps[e]?.type?.basic;
return !['weather','clock'].includes(e);
})
.sort((a, b) => {
return a.localeCompare(b);
})
.map((e) => {
const obj = result(
apps[e].type.basic.title,
apps[e].type.basic.linkedUrl,
apps[e].type.basic.title,
apps[e].type?.basic?.title || apps[e].type.custom?.tile || e,
apps[e]?.type.basic?.linkedUrl || apps[e]?.type.custom?.linkedUrl || '',
apps[e]?.type?.basic?.title || apps[e].type.custom?.tile || e,
null
);
applications.push(obj);

View File

@ -3,27 +3,6 @@ import _ from 'lodash';
import { reduceState } from '../state/base';
import useContactState, { ContactState } from '../state/contact';
export const ContactReducer = (json) => {
const data: ContactUpdate = _.get(json, 'contact-update', false);
if (data) {
reduceState<ContactState, ContactUpdate>(useContactState, data, [
initial,
add,
remove,
edit,
setPublic
]);
}
// TODO: better isolation
const res = _.get(json, 'resource', false);
if (res) {
useContactState.setState({
nackedContacts: useContactState.getState().nackedContacts.add(`~${res.ship}`)
});
}
};
const initial = (json: ContactUpdate, state: ContactState): ContactState => {
const data = _.get(json, 'initial', false);
if (data) {
@ -65,12 +44,20 @@ export const edit = (json: ContactUpdate, state: ContactState): ContactState =>
}
const value = data['edit-field'][field];
if(field === 'add-group') {
state.contacts[ship].groups.push(value);
if (typeof value !== 'string') {
state.contacts[ship].groups.push(`/ship/${Object.values(value).join('/')}`);
} else if (!(state.contacts[ship].groups.includes(value))) {
state.contacts[ship].groups.push(value);
}
} else if (field === 'remove-group') {
if (typeof value !== 'string') {
state.contacts[ship].groups =
state.contacts[ship].groups.filter(g => g !== `/ship/${Object.values(value).join('/')}`);
} else {
state.contacts[ship].groups =
state.contacts[ship].groups.filter(g => g !== value);
}
} else {
state.contacts[ship][field] = value;
}
@ -84,3 +71,23 @@ const setPublic = (json: ContactUpdate, state: ContactState): ContactState => {
return state;
};
export const ContactReducer = (json) => {
const data: ContactUpdate = _.get(json, 'contact-update', false);
if (data) {
reduceState<ContactState, ContactUpdate>(useContactState, data, [
initial,
add,
remove,
edit,
setPublic
]);
}
// TODO: better isolation
const res = _.get(json, 'resource', false);
if (res) {
useContactState.setState({
nackedContacts: useContactState.getState().nackedContacts.add(`~${res.ship}`)
});
}
};

View File

@ -65,5 +65,5 @@ RelativeTime.args = {
size: 24,
sigilPadding: 6,
isRelativeTime: true,
date: date - 3600000
date: Date.now() - 3600000
};

View File

@ -56,9 +56,10 @@ export function ViewProfile(props: any): ReactElement {
<Col gapY={3} mb={3} mt={6} alignItems='flex-start'>
<Text gray>Pinned Groups</Text>
<Col>
{contact?.groups.slice().sort(lengthOrder).map(g => (
{contact?.groups.slice().sort(lengthOrder).map((g, i) => (
<GroupLink
api={api}
key={i}
resource={g}
measure={() => {}}
/>

View File

@ -81,12 +81,15 @@ export class OmniboxResult extends Component<OmniboxResultProps, OmniboxResultSt
if (
defaultApps.includes(icon.toLowerCase()) ||
icon.toLowerCase() === 'links' ||
icon.toLowerCase() === 'terminal'
icon.toLowerCase() === 'terminal' ||
icon === 'btc-wallet'
) {
if (icon === 'Link') {
icon = 'Collection';
} else if (icon === 'Terminal') {
icon = 'Dojo';
} else if (icon === 'btc-wallet') {
icon = 'Bitcoin';
}
graphic = (
<Icon

View File

@ -13,7 +13,7 @@ import {
Tr,
Td
} from '@tlon/indigo-react';
import { Content } from '@urbit/api';
import { Content, CodeContent } from '@urbit/api';
import _ from 'lodash';
import { BlockContent, Content as AstContent, Parent, Root } from 'ts-mdast';
import React from 'react';
@ -23,7 +23,6 @@ import { PropFunc } from '~/types';
import { PermalinkEmbed } from '~/views/apps/permalinks/embed';
import { Mention } from '~/views/components/MentionText';
import RemoteContent from '~/views/components/RemoteContent';
import CodeContent from './content/code';
import { parseTall, parseWide } from './parse';
type StitchMode = 'merge' | 'block' | 'inline';
@ -245,6 +244,7 @@ const renderers = {
return (
<Text
mono
fontWeight='inherit'
p={1}
backgroundColor="washedGray"
fontSize={0}
@ -309,6 +309,7 @@ const renderers = {
className="clamp-message"
display="block"
borderRadius={1}
fontWeight='inherit'
mono
fontSize={0}
backgroundColor="washedGray"

View File

@ -1,50 +0,0 @@
import { Box, Text } from '@tlon/indigo-react';
import React, { Component } from 'react';
export default class CodeContent extends Component {
render() {
const { props } = this;
const content = props.content;
const outputElement =
(Boolean(content.code.output) &&
content.code.output.length && content.code.output.length > 0) ?
(
<Text
display='block'
fontSize={0}
mono
p={1}
my={0}
borderRadius={1}
overflow='auto'
maxHeight='10em'
maxWidth='100%'
style={{ whiteSpace: 'pre' }}
backgroundColor='washedGray'
>
{content.code.output.join('\n')}
</Text>
) : null;
return (
<Box my={2}>
<Text
display='block'
mono
my={0}
p={1}
borderRadius={1}
overflow='auto'
maxHeight='10em'
maxWidth='100%'
fontSize={0}
style={{ whiteSpace: 'pre' }}
>
{content.code.expression}
</Text>
{outputElement}
</Box>
);
}
}

View File

@ -1,189 +0,0 @@
import { Anchor, Row, Text } from '@tlon/indigo-react';
import React from 'react';
import ReactMarkdown from 'react-markdown';
import RemarkDisableTokenizers from 'remark-disable-tokenizers';
import urbitOb from 'urbit-ob';
import { GroupLink } from '~/views/components/GroupLink';
const DISABLED_BLOCK_TOKENS = [
'indentedCode',
'atxHeading',
'thematicBreak',
'list',
'setextHeading',
'html',
'definition',
'table'
];
const DISABLED_INLINE_TOKENS = [
'autoLink',
'url',
'email',
'reference'
];
const renderers = {
inlineCode: ({ language, value }) => {
return (
<Text
mono
p={1}
backgroundColor='washedGray'
fontSize={0}
style={{ whiteSpace: 'preWrap' }}
>
{value}
</Text>
);
},
blockquote: ({ children }) => {
return (
<Text
lineHeight="20px"
display="block"
borderLeft="1px solid"
color="black"
paddingLeft={2}
>
{children}
</Text>
);
},
paragraph: ({ children }) => {
return (
<Text fontSize={1} lineHeight={'20px'}>
{children}
</Text>
);
},
code: ({ language, value }) => {
return (
<Text
p={1}
className='clamp-message'
display='block'
borderRadius={1}
mono
fontSize={0}
backgroundColor='washedGray'
overflowX='auto'
style={{ whiteSpace: 'pre' }}
>
{value}
</Text>
);
},
link: (props) => {
return <Anchor src={props.href} borderBottom={1} color="black">{props.children}</Anchor>
},
list: ({depth, children}) => {
return <Text my={2} display='block' fontSize={1} ml={depth ? (2 * depth) : 0} lineHeight={'20px'}>{children}</Text>
}
};
const MessageMarkdown = React.memo((props) => {
const { source, allowHeaders, allowLists, ...rest } = props;
const blockCode = source.split('```');
const codeLines = blockCode.map((codes) => codes.split('\n'));
let lines = [];
if (allowLists) {
lines.push(source);
} else {
lines = codeLines.reduce((acc, val, i) => {
if (i % 2 === 1) {
return [...acc, `\`\`\`${val.join('\n')}\`\`\``];
} else {
return [...acc, ...val];
}
}, []);
}
const modifiedBlockTokens = DISABLED_BLOCK_TOKENS.filter(e => {
if (allowHeaders && allowLists) {
return (e in ["setextHeading", "atxHeading", "list"])
} else if (allowHeaders) {
return (e in ["setextHeading", "atxHeading"])
} else if (allowLists) {
return (e === "list")
}
})
return lines.map((line, i) => (
<React.Fragment key={i}>
{i !== 0 && <Row height={2} />}
<ReactMarkdown
{...rest}
source={line}
unwrapDisallowed={true}
renderers={renderers}
allowNode={(node, index, parent) => {
if (
node.type === 'blockquote' &&
parent.type === 'root' &&
node.children.length &&
node.children[0].type === 'paragraph' &&
node.children[0].position.start.offset < 2
) {
node.children[0].children[0].value =
'>' + node.children[0].children[0].value;
return false;
}
return true;
}}
plugins={[
[
RemarkDisableTokenizers,
{
block: modifiedBlockTokens,
inline: DISABLED_INLINE_TOKENS
}
]
]}
/>
</React.Fragment>
));
});
export default function TextContent(props) {
const content = props.content;
const allowHeaders = props.allowHeaders;
const allowLists = props.allowLists;
const group = content.text.trim().match(
/([~][/])?(~[a-z]{3,6})(-[a-z]{6})?([/])(([a-z0-9-])+([/-])?)+/
);
const isGroupLink =
group !== null && // matched possible chatroom
group[2].length > 2 && // possible ship?
urbitOb.isValidPatp(group[2]) && // valid patp?
group[0] === content.text.trim(); // entire message is room name?
if (isGroupLink) {
const resource = `/ship/${content.text.trim()}`;
return (
<GroupLink
resource={resource}
api={props.api}
pl={2}
my={2}
border={1}
borderRadius={2}
borderColor='washedGray'
/>
);
} else {
return (
<Text
flexShrink={0}
color='black'
fontSize={props.fontSize ? props.fontSize : '14px'}
lineHeight={props.lineHeight ? props.lineHeight : '20px'}
style={{ overflowWrap: 'break-word' }}
>
<MessageMarkdown source={content.text} allowHeaders={allowHeaders} allowLists={allowLists} />
</Text>
);
}
}

View File

@ -53,8 +53,22 @@ export function NewChannel(props: NewChannelProps): ReactElement {
const groups = useGroupState(state => state.groups);
const waiter = useWaitForProps({ groups }, 5000);
const channelName = (values) => {
if (values.name)
return values.name;
if (!values.name && workspace?.type === 'messages') {
const joinedShips = values.ships
.filter(Boolean)
.map(ship => `~${deSig(ship)}`)
.join(', ')
.concat(`, ~${deSig(window.ship)}`);
return joinedShips;
}
return values.moduleType;
};
const onSubmit = async (values: FormSchema, actions) => {
const name = values.name ? values.name : values.moduleType;
const name = channelName(values);
const resId: string =
stringToSymbol(values.name) +
(workspace?.type !== 'messages'

View File

@ -1,3 +1,4 @@
import _ from 'lodash';
import { Box, Col, Icon, Text } from '@tlon/indigo-react';
import { Association } from '@urbit/api/metadata';
import React, { ReactElement, ReactNode, useCallback, useState } from 'react';
@ -8,6 +9,7 @@ import GlobalApi from '~/logic/api/global';
import { isWriter } from '~/logic/lib/group';
import { getItemTitle } from '~/logic/lib/util';
import useContactState from '~/logic/state/contact';
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
import useGroupState from '~/logic/state/group';
import { Dropdown } from '~/views/components/Dropdown';
import RichText from '~/views/components/RichText';
@ -23,6 +25,35 @@ const TruncatedText = styled(RichText)`
}
`;
const participantNames = (str: string, contacts, hideNicknames) => {
if (_.includes(str, ',') && _.startsWith(str, '~')) {
const names = _.split(str, ', ');
return names.map((name, idx) => {
if (urbitOb.isValidPatp(name)) {
if (contacts[name]?.nickname && !hideNicknames)
return (
<Text key={name} fontSize={2} fontWeight='600'>
{contacts[name]?.nickname}
{idx + 1 != names.length ? ', ' : null}
</Text>
);
return (
<Text key={name} mono fontSize={2} fontWeight='600'>
{name}
<Text fontSize={2} fontWeight='600'>
{idx + 1 != names.length ? ', ' : null}
</Text>
</Text>
);
} else {
return name;
}
});
} else {
return str;
}
};
type ResourceSkeletonProps = {
association: Association;
api: GlobalApi;
@ -40,6 +71,7 @@ export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement {
}
const rid = association.resource;
const groups = useGroupState(state => state.groups);
const { hideNicknames } = useSettingsState(selectCalmState);
const group = groups[association.group];
let workspace = association.group;
const [actionsWidth, setActionsWidth] = useState(0);
@ -58,7 +90,7 @@ export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement {
const contacts = useContactState(state => state.contacts);
if (urbitOb.isValidPatp(title)) {
if (urbitOb.isValidPatp(title) && !hideNicknames) {
recipient = title;
title = (contacts?.[title]?.nickname) ? contacts[title].nickname : title;
} else {
@ -107,13 +139,16 @@ export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement {
ml='1'
flexShrink={1}
>
{title}
{workspace === '/messages' && !urbitOb.isValidPatp(title)
? participantNames(title, contacts, hideNicknames)
: title}
</Text>
);
const Description = () => (
<TruncatedText
mono={workspace === '/messages' && !urbitOb.isValidPatp(title)}
display={['none','inline']}
mono={workspace === '/messages' && !association?.metadata?.description}
color='gray'
mb={0}
minWidth={0}
@ -121,7 +156,7 @@ export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement {
flexShrink={1}
disableRemoteContent
>
{workspace === '/messages'
{workspace === '/messages' && !association?.metadata?.description
? recipient
: association?.metadata?.description}
</TruncatedText>

View File

@ -1,20 +1,22 @@
import React, { ReactElement, useRef, ReactNode } from 'react';
import _ from 'lodash';
import React, { useRef } from 'react';
import urbitOb from 'urbit-ob';
import { Icon, Row, Box, Text, BaseImage } from '@tlon/indigo-react';
import { Groups, Association, Rolodex, cite } from '@urbit/api';
import { Association, cite } from '@urbit/api';
import { HoverBoxLink } from '~/views/components/HoverBox';
import { Sigil } from '~/logic/lib/sigil';
import { useTutorialModal } from '~/views/components/useTutorialModal';
import { TUTORIAL_HOST, TUTORIAL_GROUP } from '~/logic/lib/tutorialModal';
import { Workspace } from '~/types/workspace';
import { useContact } from '~/logic/state/contact';
import useContactState, { useContact } from '~/logic/state/contact';
import { getItemTitle, getModuleIcon, uxToHex } from '~/logic/lib/util';
import useGroupState from '~/logic/state/group';
import Dot from '~/views/components/Dot';
import { SidebarAppConfigs } from './types';
import {useHarkDm} from '~/logic/state/hark';
import { useHarkDm } from '~/logic/state/hark';
import useSettingsState from '~/logic/state/settings';
function SidebarItemBase(props: {
to: string;
@ -23,7 +25,7 @@ function SidebarItemBase(props: {
hasUnread: boolean;
isSynced?: boolean;
children: ReactNode;
title: string;
title: string | ReactNode;
mono?: boolean;
}) {
const {
@ -34,7 +36,7 @@ function SidebarItemBase(props: {
hasNotification,
hasUnread,
isSynced = false,
mono = false,
mono = false
} = props;
const color = isSynced ? 'black' : 'lightGray';
@ -42,7 +44,7 @@ function SidebarItemBase(props: {
return (
<HoverBoxLink
//ref={anchorRef}
// ref={anchorRef}
to={to}
bg="white"
bgActive="washedGray"
@ -102,9 +104,9 @@ export function SidebarDmItem(props: {
}) {
const { ship, selected = false } = props;
const contact = useContact(ship);
const title = contact?.nickname || (cite(ship) ?? ship)
const title = contact?.nickname || (cite(ship) ?? ship);
const hideAvatars = false;
const { unreads } = useHarkDm(ship) || { unreads : 0 };
const { unreads } = useHarkDm(ship) || { unreads: 0 };
const img =
contact?.avatar && !hideAvatars ? (
<BaseImage
@ -128,7 +130,7 @@ export function SidebarDmItem(props: {
<SidebarItemBase
selected={selected}
hasNotification={false}
hasUnread={unreads as number > 0}
hasUnread={(unreads as number) > 0}
to={`/~landscape/messages/dm/${ship}`}
title={title}
mono={!contact?.nickname}
@ -148,7 +150,8 @@ export function SidebarAssociationItem(props: {
workspace: Workspace;
}) {
const { association, path, selected, apps } = props;
let title = getItemTitle(association) || '';
const title = getItemTitle(association) || '';
const color = `#${uxToHex(association?.metadata?.color || '0x0')}`;
const appName = association?.['app-name'];
let mod = appName;
if (association?.metadata?.config && 'graph' in association.metadata.config) {
@ -156,7 +159,9 @@ export function SidebarAssociationItem(props: {
}
const rid = association?.resource;
const groupPath = association?.group;
const groups = useGroupState((state) => state.groups);
const groups = useGroupState(state => state.groups);
const { hideNicknames } = useSettingsState(s => s.calm);
const contacts = useContactState(s => s.contacts);
const anchorRef = useRef<HTMLAnchorElement>(null);
useTutorialModal(
mod as any,
@ -194,19 +199,62 @@ export function SidebarAssociationItem(props: {
return null;
}
const participantNames = (str: string, color: string) => {
if (_.includes(str, ',') && _.startsWith(str, '~')) {
const names = _.split(str, ', ');
return names.map((name, idx) => {
if (urbitOb.isValidPatp(name)) {
if (contacts[name]?.nickname && !hideNicknames)
return (
<Text key={name} color={color}>
{contacts[name]?.nickname}
{idx + 1 != names.length ? ', ' : null}
</Text>
);
return (
<Text key={name} mono color={color}>
{name}
<Text color={color}>{idx + 1 != names.length ? ', ' : null}</Text>
</Text>
);
} else {
return name;
}
});
} else {
return str;
}
};
return (
<SidebarItemBase
to={to}
selected={selected}
hasUnread={hasUnread}
title={title}
title={
DM && !urbitOb.isValidPatp(title)
? participantNames(title, color)
: title
}
hasNotification={hasNotification}
>
<Icon
display="block"
color={isSynced ? 'black' : 'lightGray'}
icon={getModuleIcon(mod)}
/>
{DM ? (
<Box
flexShrink={0}
height={16}
width={16}
borderRadius={2}
backgroundColor={
`#${uxToHex(props?.association?.metadata?.color)}` || '#000000'
}
/>
) : (
<Icon
display="block"
color={isSynced ? 'black' : 'lightGray'}
icon={getModuleIcon(mod)}
/>
)}
</SidebarItemBase>
);
}