Merge branch 'release/next-js' into release/next-userspace

This commit is contained in:
Matilde Park 2021-03-15 13:18:04 -04:00
commit dfe186b96e
38 changed files with 522 additions and 365 deletions

View File

@ -94,7 +94,11 @@ module.exports = {
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/typescript', '@babel/preset-react'],
presets: ['@babel/preset-env', '@babel/typescript', ['@babel/preset-react', {
runtime: 'automatic',
development: true,
importSource: '@welldone-software/why-did-you-render',
}]],
plugins: [
'@babel/transform-runtime',
'@babel/plugin-proposal-object-rest-spread',

View File

@ -1995,6 +1995,15 @@
"@xtuc/long": "4.2.2"
}
},
"@welldone-software/why-did-you-render": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@welldone-software/why-did-you-render/-/why-did-you-render-6.1.0.tgz",
"integrity": "sha512-0s+PuKQ4v9VV1SZSM6iS7d2T7X288T3DF+K8yfkFAhI31HhJGGH1SY1ssVm+LqjSMyrVWT60ZF5r0qUsO0Z9Lw==",
"dev": true,
"requires": {
"lodash": "^4"
}
},
"@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",

View File

@ -71,6 +71,7 @@
"@types/yup": "^0.29.11",
"@typescript-eslint/eslint-plugin": "^4.15.0",
"@urbit/eslint-config": "file:../npm/eslint-config",
"@welldone-software/why-did-you-render": "^6.1.0",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.2.2",
"babel-plugin-lodash": "^3.3.4",

View File

@ -1,3 +1,4 @@
import './wdyr';
import * as React from 'react';
import * as ReactDOM from 'react-dom';

View File

@ -77,7 +77,6 @@ export default class MetadataApi extends BaseApi<StoreState> {
tempChannel.delete();
},
(ev: any) => {
console.log(ev);
if ('metadata-hook-update' in ev) {
done = true;
tempChannel.delete();

View File

@ -23,7 +23,8 @@ export const Sigil = memo(
size,
svgClass = '',
icon = false,
padding = 0
padding = 0,
display = 'inline-block'
}) => {
const innerSize = Number(size) - 2 * padding;
const paddingPx = `${padding}px`;
@ -34,14 +35,14 @@ export const Sigil = memo(
<Box
backgroundColor={color}
borderRadius={icon ? '1' : '0'}
display='inline-block'
display={display}
height={size}
width={size}
className={classes}
/>
) : (
<Box
display='inline-block'
display={display}
borderRadius={icon ? '1' : '0'}
flexBasis={size}
backgroundColor={color}

View File

@ -133,7 +133,7 @@ function graphWatchSelf(json: any, state: HarkState): HarkState {
function readAll(json: any, state: HarkState): HarkState {
const data = _.get(json, 'read-all');
if(data) {
clearState(state);
state = clearState(state);
}
return state;
}
@ -149,7 +149,7 @@ function removeGraph(json: any, state: HarkState): HarkState {
function seenIndex(json: any, state: HarkState): HarkState {
const data = _.get(json, 'seen-index');
if(data) {
updateNotificationStats(state, data.index, 'last', () => data.time);
state = updateNotificationStats(state, data.index, 'last', () => data.time);
}
return state;
}
@ -157,7 +157,7 @@ function seenIndex(json: any, state: HarkState): HarkState {
function readEach(json: any, state: HarkState): HarkState {
const data = _.get(json, 'read-each');
if (data) {
updateUnreads(state, data.index, u => u.delete(data.target));
state = updateUnreads(state, data.index, u => u.delete(data.target));
}
return state;
}
@ -165,7 +165,7 @@ function readEach(json: any, state: HarkState): HarkState {
function readSince(json: any, state: HarkState): HarkState {
const data = _.get(json, 'read-count');
if(data) {
updateUnreadCount(state, data, () => 0);
state = updateUnreadCount(state, data, () => 0);
}
return state;
}
@ -173,7 +173,7 @@ function readSince(json: any, state: HarkState): HarkState {
function unreadSince(json: any, state: HarkState): HarkState {
const data = _.get(json, 'unread-count');
if(data) {
updateUnreadCount(state, data.index, u => u + 1);
state = updateUnreadCount(state, data.index, u => u + 1);
}
return state;
}
@ -181,7 +181,7 @@ function unreadSince(json: any, state: HarkState): HarkState {
function unreadEach(json: any, state: HarkState): HarkState {
const data = _.get(json, 'unread-each');
if(data) {
updateUnreads(state, data.index, us => us.add(data.target));
state = updateUnreads(state, data.index, us => us.add(data.target));
}
return state;
}
@ -191,13 +191,14 @@ function unreads(json: any, state: HarkState): HarkState {
if(data) {
data.forEach(({ index, stats }) => {
const { unreads, notifications, last } = stats;
updateNotificationStats(state, index, 'notifications', x => x + notifications);
updateNotificationStats(state, index, 'last', () => last);
state = updateNotificationStats(state, index, 'notifications', x => x + notifications);
state = updateNotificationStats(state, index, 'last', () => last);
if('count' in unreads) {
updateUnreadCount(state, index, (u = 0) => u + unreads.count);
state = updateUnreadCount(state, index, (u = 0) => u + unreads.count);
} else {
state = updateUnreads(state, index, s => new Set());
unreads.each.forEach((u: string) => {
updateUnreads(state, index, s => s.add(u));
state = updateUnreads(state, index, s => s.add(u));
});
}
});
@ -205,7 +206,7 @@ function unreads(json: any, state: HarkState): HarkState {
return state;
}
function clearState(state: HarkState) {
function clearState(state: HarkState): HarkState {
const initialState = {
notifications: new BigIntOrderedMap<Timebox>(),
archivedNotifications: new BigIntOrderedMap<Timebox>(),
@ -225,6 +226,7 @@ function clearState(state: HarkState) {
Object.keys(initialState).forEach((key) => {
state[key] = initialState[key];
});
return state;
}
function updateUnreadCount(state: HarkState, index: NotifIndex, count: (c: number) => number): HarkState {
@ -242,10 +244,9 @@ function updateUnreads(state: HarkState, index: NotifIndex, f: (us: Set<string>)
if(!('graph' in index)) {
return state;
}
const unreads = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], new Set<string>());
const oldSize = unreads.size;
let unreads = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], new Set<string>());
f(unreads);
const newSize = unreads.size;
_.set(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], unreads);
return state;
}
@ -276,7 +277,6 @@ function added(json: any, state: HarkState): HarkState {
);
if (arrIdx !== -1) {
if (timebox[arrIdx]?.notification?.read) {
// TODO this is additive, and with a persistent state it keeps incrementing
state = updateNotificationStats(state, index, 'notifications', x => x+1);
}
timebox[arrIdx] = { index, notification };
@ -361,7 +361,7 @@ function read(json: any, state: HarkState): HarkState {
const data = _.get(json, 'read-note', false);
if (data) {
const { time, index } = data;
updateNotificationStats(state, index, 'notifications', x => x-1);
state = updateNotificationStats(state, index, 'notifications', x => x-1);
setRead(time, index, true, state);
}
return state;
@ -371,7 +371,7 @@ function unread(json: any, state: HarkState): HarkState {
const data = _.get(json, 'unread-note', false);
if (data) {
const { time, index } = data;
updateNotificationStats(state, index, 'notifications', x => x+1);
state = updateNotificationStats(state, index, 'notifications', x => x+1);
setRead(time, index, false, state);
}
return state;
@ -397,7 +397,7 @@ function archive(json: any, state: HarkState): HarkState {
state.notifications.set(time, unarchived);
}
const newlyRead = archived.filter(x => !x.notification.read).length;
updateNotificationStats(state, index, 'notifications', x => x - newlyRead);
state = updateNotificationStats(state, index, 'notifications', x => x - newlyRead);
}
return state;
}

View File

@ -34,7 +34,7 @@ export const reduceState = <
export let stateStorageKeys: string[] = [];
export const stateStorageKey = (stateName: string) => {
stateName = `Landcape${stateName}State`;
stateName = `Landscape${stateName}State`;
stateStorageKeys = [...new Set([...stateStorageKeys, stateName])];
return stateName;
};

View File

@ -24,7 +24,7 @@ type ChatInputProps = IuseStorage & {
message: string;
deleteMessage(): void;
hideAvatars: boolean;
}
};
interface ChatInputState {
inCodeMode: boolean;
@ -62,18 +62,21 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
const { props, state } = this;
const [, , ship, name] = props.station.split('/');
if (state.inCodeMode) {
this.setState({
this.setState(
{
inCodeMode: false
}, async () => {
},
async () => {
const output = await props.api.graph.eval(text);
const contents: Content[] = [{ code: { output, expression: text } }];
const post = createPost(contents);
props.api.graph.addPost(ship, name, post);
});
}
);
return;
}
const post = createPost(tokenizeMessage((text)));
const post = createPost(tokenizeMessage(text));
props.deleteMessage();
@ -110,7 +113,8 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
return;
}
Array.from(files).forEach((file) => {
this.props.uploadDefault(file)
this.props
.uploadDefault(file)
.then(this.uploadSuccess)
.catch(this.uploadError);
});
@ -119,32 +123,40 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
render() {
const { props, state } = this;
const color = props.ourContact
? uxToHex(props.ourContact.color) : '000000';
const color = props.ourContact ? uxToHex(props.ourContact.color) : '000000';
const sigilClass = props.ourContact
? '' : 'mix-blend-diff';
const sigilClass = props.ourContact ? '' : 'mix-blend-diff';
const avatar = (
props.ourContact &&
((props.ourContact?.avatar) && !props.hideAvatars)
)
? <BaseImage
const avatar =
props.ourContact && props.ourContact?.avatar && !props.hideAvatars ? (
<BaseImage
src={props.ourContact.avatar}
height={16}
width={16}
height={24}
width={24}
style={{ objectFit: 'cover' }}
borderRadius={1}
display='inline-block'
/>
: <Sigil
) : (
<Box
width={24}
height={24}
display='flex'
justifyContent='center'
alignItems='center'
backgroundColor={`#${color}`}
borderRadius={1}
>
<Sigil
ship={window.ship}
size={16}
color={`#${color}`}
classes={sigilClass}
icon
padding={2}
/>;
/>
</Box>
);
return (
<Row
@ -158,7 +170,7 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
className='cf'
zIndex={0}
>
<Row p='2' alignItems='center'>
<Row p='12px 4px 12px 12px' alignItems='center'>
{avatar}
</Row>
<ChatEditor
@ -170,31 +182,23 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
onPaste={this.onPaste.bind(this)}
placeholder='Message...'
/>
<Box
mx={2}
flexShrink={0}
height='16px'
width='16px'
flexBasis='16px'
>
{this.props.canUpload
? this.props.uploading
? <LoadingSpinner />
: <Icon icon='Links'
width="16"
height="16"
onClick={() => this.props.promptUpload().then(this.uploadSuccess)}
/>
: null
<Box mx={2} flexShrink={0} height='16px' width='16px' flexBasis='16px'>
{this.props.canUpload ? (
this.props.uploading ? (
<LoadingSpinner />
) : (
<Icon
icon='Links'
width='16'
height='16'
onClick={() =>
this.props.promptUpload().then(this.uploadSuccess)
}
/>
)
) : null}
</Box>
<Box
mr={2}
flexShrink={0}
height='16px'
width='16px'
flexBasis='16px'
>
<Box mr={2} flexShrink={0} height='16px' width='16px' flexBasis='16px'>
<Icon
icon='Dojo'
onClick={this.toggleCode}
@ -206,4 +210,6 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
}
}
export default withLocalState(withStorage(ChatInput, { accept: 'image/*' }), ['hideAvatars']);
export default withLocalState(withStorage(ChatInput, { accept: 'image/*' }), [
'hideAvatars'
]);

View File

@ -10,7 +10,7 @@ import React, {
import moment from 'moment';
import _ from 'lodash';
import VisibilitySensor from 'react-visibility-sensor';
import { Box, Row, Text, Rule, BaseImage } from '@tlon/indigo-react';
import { Box, Row, Text, Rule, BaseImage, Icon, Col } from '@tlon/indigo-react';
import { Sigil } from '~/logic/lib/sigil';
import OverlaySigil from '~/views/components/OverlaySigil';
import {
@ -33,9 +33,10 @@ import TextContent from './content/text';
import CodeContent from './content/code';
import RemoteContent from '~/views/components/RemoteContent';
import { Mention } from '~/views/components/MentionText';
import { Dropdown } from '~/views/components/Dropdown';
import styled from 'styled-components';
import useLocalState from '~/logic/state/local';
import useSettingsState, {selectCalmState} from "~/logic/state/settings";
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
import Timestamp from '~/views/components/Timestamp';
import useContactState from '~/logic/state/contact';
import { useIdlingState } from '~/logic/lib/idling';
@ -64,7 +65,8 @@ export const DayBreak = ({ when, shimTop = false }: DayBreakProps) => (
</Row>
);
export const UnreadMarker = React.forwardRef(({ dayBreak, when, api, association }, ref) => {
export const UnreadMarker = React.forwardRef(
({ dayBreak, when, api, association }, ref) => {
const [visible, setVisible] = useState(false);
const idling = useIdlingState();
const dismiss = useCallback(() => {
@ -96,7 +98,123 @@ export const UnreadMarker = React.forwardRef(({ dayBreak, when, api, association
</VisibilitySensor>
<Rule borderColor='lightBlue' />
</Row>
)});
);
}
);
const MessageActionItem = (props) => {
return (
<Row
color='black'
cursor='pointer'
fontSize={1}
fontWeight='500'
px={3}
py={2}
onClick={props.onClick}
>
<Text fontWeight='500' color={props.color}>
{props.children}
</Text>
</Row>
);
};
const MessageActions = ({ api, history, msg, group }) => {
const isAdmin = () => group.tags.role.admin.has(window.ship);
const isOwn = () => msg.author === window.ship;
return (
<Box
borderRadius={1}
background='white'
border='1px solid'
borderColor='lightGray'
position='absolute'
top='-12px'
right={2}
>
<Row>
{isOwn() ? (
<Box
padding={1}
size={'24px'}
cursor='pointer'
onClick={(e) => console.log(e)}
>
<Icon icon='NullIcon' size={3} />
</Box>
) : null}
<Box
padding={1}
size={'24px'}
cursor='pointer'
onClick={(e) => console.log(e)}
>
<Icon icon='Chat' size={3} />
</Box>
<Dropdown
dropWidth='250px'
width='auto'
alignY='top'
alignX='right'
flexShrink={'0'}
offsetY={8}
offsetX={-24}
options={
<Col
py={2}
backgroundColor='white'
color='washedGray'
border={1}
borderRadius={2}
borderColor='lightGray'
boxShadow='0px 0px 0px 3px'
>
{isOwn() ? (
<MessageActionItem onClick={(e) => console.log(e)}>
Edit Message
</MessageActionItem>
) : null}
<MessageActionItem onClick={(e) => console.log(e)}>
Reply
</MessageActionItem>
<MessageActionItem onClick={(e) => console.log(e)}>
Copy Message Link
</MessageActionItem>
{isAdmin() || isOwn() ? (
<MessageActionItem onClick={(e) => console.log(e)} color='red'>
Delete Message
</MessageActionItem>
) : null}
<MessageActionItem onClick={(e) => console.log(e)}>
View Signature
</MessageActionItem>
</Col>
}
>
<Box padding={1} size={'24px'} cursor='pointer'>
<Icon icon='Menu' size={3} />
</Box>
</Dropdown>
</Row>
</Box>
);
};
const MessageWrapper = (props) => {
const { hovering, bind } = useHovering();
return (
<Box
py='1'
backgroundColor={hovering ? 'washedGray' : 'transparent'}
position='relative'
{...bind}
>
{props.children}
{/* {hovering ? <MessageActions {...props} /> : null} */}
</Box>
);
};
interface ChatMessageProps {
msg: Post;
@ -126,8 +244,7 @@ class ChatMessage extends Component<ChatMessageProps> {
this.divRef = React.createRef();
}
componentDidMount() {
}
componentDidMount() {}
render() {
const {
@ -146,7 +263,7 @@ class ChatMessage extends Component<ChatMessageProps> {
history,
api,
highlighted,
fontSize,
fontSize
} = this.props;
let { renderSigil } = this.props;
@ -170,7 +287,6 @@ class ChatMessage extends Component<ChatMessageProps> {
.unix(msg['time-sent'] / 1000)
.format(renderSigil ? 'h:mm A' : 'h:mm');
const messageProps = {
msg,
timestamp,
@ -183,7 +299,7 @@ class ChatMessage extends Component<ChatMessageProps> {
api,
scrollWindow,
highlighted,
fontSize,
fontSize
};
const unreadContainerStyle = {
@ -194,7 +310,7 @@ class ChatMessage extends Component<ChatMessageProps> {
<Box
ref={this.props.innerRef}
pt={renderSigil ? 2 : 0}
pb={isLastMessage ? 4 : 2}
pb={isLastMessage ? '20px' : 0}
className={containerClass}
backgroundColor={highlighted ? 'blue' : 'white'}
style={style}
@ -203,12 +319,14 @@ class ChatMessage extends Component<ChatMessageProps> {
<DayBreak when={msg['time-sent']} shimTop={renderSigil} />
) : null}
{renderSigil ? (
<>
<MessageAuthor pb={'2px'} {...messageProps} />
<Message pl={5} pr={4} {...messageProps} />
</>
<MessageWrapper {...messageProps}>
<MessageAuthor pb={1} {...messageProps} />
<Message pl={'44px'} pr={4} {...messageProps} />
</MessageWrapper>
) : (
<Message pl={5} pr={4} timestampHover {...messageProps} />
<MessageWrapper {...messageProps}>
<Message pl={'44px'} pr={4} timestampHover {...messageProps} />
</MessageWrapper>
)}
<Box style={unreadContainerStyle}>
{isLastRead ? (
@ -226,7 +344,9 @@ class ChatMessage extends Component<ChatMessageProps> {
}
}
export default React.forwardRef((props, ref) => <ChatMessage {...props} innerRef={ref} />);
export default React.forwardRef((props, ref) => (
<ChatMessage {...props} innerRef={ref} />
));
export const MessageAuthor = ({
timestamp,
@ -239,9 +359,9 @@ export const MessageAuthor = ({
}) => {
const osDark = useLocalState((state) => state.dark);
const theme = useSettingsState(s => s.display.theme);
const theme = useSettingsState((s) => s.display.theme);
const dark = theme === 'dark' || (theme === 'auto' && osDark);
const contacts = useContactState(state => state.contacts);
const contacts = useContactState((state) => state.contacts);
const datestamp = moment
.unix(msg['time-sent'] / 1000)
@ -291,19 +411,30 @@ export const MessageAuthor = ({
display='inline-block'
style={{ objectFit: 'cover' }}
src={contact.avatar}
height={16}
width={16}
height={24}
width={24}
borderRadius={1}
/>
) : (
<Box
width={24}
height={24}
display='flex'
justifyContent='center'
alignItems='center'
backgroundColor={color}
borderRadius={1}
>
<Sigil
ship={msg.author}
size={16}
size={12}
display='block'
color={color}
classes={sigilClass}
icon
padding={2}
padding={0}
/>
</Box>
);
return (
<Box display='flex' alignItems='center' {...rest}>
@ -311,9 +442,9 @@ export const MessageAuthor = ({
onClick={() => {
setShowOverlay(true);
}}
height={16}
height={24}
pr={2}
pl={2}
pl={'12px'}
cursor='pointer'
position='relative'
>
@ -340,10 +471,10 @@ export const MessageAuthor = ({
pt={1}
pb={1}
display='flex'
alignItems='center'
alignItems='baseline'
>
<Text
fontSize={0}
fontSize={1}
mr={2}
flexShrink={0}
mono={nameMono}
@ -385,13 +516,15 @@ export const Message = ({
...rest
}) => {
const { hovering, bind } = useHovering();
const contacts = useContactState(state => state.contacts);
const contacts = useContactState((state) => state.contacts);
return (
<Box position='relative' {...rest}>
{timestampHover ? (
<Text
display={hovering ? 'block' : 'none'}
position='absolute'
width='36px'
textAlign='right'
left='0'
top='3px'
fontSize={0}
@ -408,6 +541,7 @@ export const Message = ({
case 'text':
return (
<TextContent
key={i}
api={api}
fontSize={1}
lineHeight={'20px'}
@ -415,10 +549,11 @@ export const Message = ({
/>
);
case 'code':
return <CodeContent content={content} />;
return <CodeContent key={i} content={content} />;
case 'url':
return (
<Box
key={i}
flexShrink={0}
fontSize={1}
lineHeight='20px'
@ -452,9 +587,10 @@ export const Message = ({
</Box>
);
case 'mention':
const first = (i) => (i === 0);
const first = (i) => i === 0;
return (
<Mention
key={i}
first={first(i)}
group={group}
scrollWindow={scrollWindow}

View File

@ -91,7 +91,7 @@ const MessageMarkdown = React.memo((props) => {
}, []);
return lines.map((line, i) => (
<>
<React.Fragment key={i}>
{i !== 0 && <Row height={2} />}
<ReactMarkdown
{...rest}
@ -123,7 +123,7 @@ const MessageMarkdown = React.memo((props) => {
]
]}
/>
</>
</React.Fragment>
));
});

View File

@ -32,6 +32,7 @@ import {
} from '~/logic/lib/tutorialModal';
import useLaunchState from '~/logic/state/launch';
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
import useMetadataState from '~/logic/state/metadata';
const ScrollbarLessBox = styled(Box)`
@ -48,6 +49,7 @@ export default function LaunchApp(props) {
const baseHash = useLaunchState(state => state.baseHash);
const [hashText, setHashText] = useState(baseHash);
const [exitingTut, setExitingTut] = useState(false);
const associations = useMetadataState(s => s.associations);
const hashBox = (
<Box
position={["relative", "absolute"]}
@ -78,7 +80,7 @@ export default function LaunchApp(props) {
useEffect(() => {
if(query.get('tutorial')) {
if(hasTutorialGroup(props)) {
if(hasTutorialGroup({ associations })) {
nextTutStep();
} else {
showModal();
@ -92,7 +94,7 @@ export default function LaunchApp(props) {
let { hideGroups } = useLocalState(tutSelector);
!hideGroups ? { hideGroups } = calmState : null;
const waiter = useWaitForProps(props);
const waiter = useWaitForProps({ ...props, associations });
const { modal, showModal } = useModal({
position: 'relative',
@ -105,7 +107,7 @@ export default function LaunchApp(props) {
};
const onContinue = async (e) => {
e.stopPropagation();
if(!hasTutorialGroup(props)) {
if(!hasTutorialGroup({ associations })) {
await props.api.groups.join(TUTORIAL_HOST, TUTORIAL_GROUP);
await props.api.settings.putEntry('tutorial', 'joined', Date.now());
await waiter(hasTutorialGroup);

View File

@ -148,17 +148,13 @@ export const LinkItem = (props: LinkItemProps): ReactElement => {
</Anchor>
</Text>
</Box>
<Row minWidth='0' flexShrink={0} width="100%" justifyContent="space-between" py={3} bg="white">
<Author
showImage
ship={author}
date={node.post['time-sent']}
group={group}
api={api}
></Author>
/>
<Box ml="auto">
<Link
to={node.post.pending ? '#' : `${baseUrl}/${index}`}

View File

@ -21,7 +21,7 @@ function Author(props: { patp: string; last?: boolean }): ReactElement {
const contact: Contact | undefined = contacts?.[`~${props.patp}`];
const showNickname = useShowNickname(contact);
const name = contact?.nickname || `~${props.patp}`;
const name = showNickname ? contact.nickname : `~${props.patp}`;
return (
<Text mono={!showNickname}>

View File

@ -85,7 +85,7 @@ export function ProfileHeaderImageEdit(props: any): ReactElement {
export function EditProfile(props: any): ReactElement {
const { contact, ship, api } = props;
const isPublic = useContactState(state => state.isContactPublic);
const isPublic = useContactState((state) => state.isContactPublic);
const [hideCover, setHideCover] = useState(false);
const handleHideCover = (value) => {
@ -150,7 +150,7 @@ export function EditProfile(props: any): ReactElement {
<Form width='100%' height='100%'>
<ProfileHeader>
<ProfileControls>
<Row>
<Row alignItems='baseline'>
<Button
type='submit'
display='inline'
@ -178,7 +178,11 @@ export function EditProfile(props: any): ReactElement {
</Row>
<ProfileStatus contact={contact} />
</ProfileControls>
<ProfileImages hideCover={hideCover} contact={contact} ship={ship}>
<ProfileImages
hideCover={hideCover}
contact={contact}
ship={ship}
>
<ProfileHeaderImageEdit
contact={contact}
setFieldValue={setFieldValue}
@ -203,11 +207,7 @@ export function EditProfile(props: any): ReactElement {
<MarkdownField id='bio' mb={3} />
</Col>
<Checkbox mb={3} id='isPublic' label='Public Profile' />
<GroupSearch
label='Pinned Groups'
id='groups'
publicOnly
/>
<GroupSearch label='Pinned Groups' id='groups' publicOnly />
<AsyncButton primary loadingText='Updating...' border mt={3}>
Submit
</AsyncButton>

View File

@ -15,8 +15,8 @@ export function ProfileHeader(props: any): ReactElement {
return (
<Box
border='1px solid'
borderColor='lightGray'
borderRadius='2'
borderColor='washedGray'
borderRadius='3'
overflow='hidden'
marginBottom='calc(64px + 2rem)'
>
@ -65,7 +65,7 @@ export function ProfileImages(props: any): ReactElement {
return (
<>
<Row ref={anchorRef} width='100%' height='300px' position='relative'>
<Row ref={anchorRef} width='100%' height='400px' position='relative'>
{cover}
<Center position='absolute' width='100%' height='100%'>
{props.children}
@ -74,7 +74,7 @@ export function ProfileImages(props: any): ReactElement {
<Box
height='128px'
width='128px'
borderRadius='2'
borderRadius='3'
overflow='hidden'
position='absolute'
left='50%'

View File

@ -39,7 +39,7 @@ export function ViewProfile(props: any): ReactElement {
</ProfileHeader>
<Row pb={2} alignItems='center' width='100%'>
<Center width='100%'>
<Text>
<Text fontWeight='500'>
{!hideNicknames && contact?.nickname ? contact.nickname : ''}
</Text>
</Center>
@ -51,7 +51,7 @@ export function ViewProfile(props: any): ReactElement {
</Text>
</Center>
</Row>
<Col pb={2} alignItems='center' justifyContent='center' width='100%'>
<Col pb={2} mt='3' alignItems='center' justifyContent='center' width='100%'>
<Center flexDirection='column' maxWidth='32rem'>
<RichText width='100%' disableRemoteContent>
{contact?.bio ? contact.bio : ''}

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Box, Text, Col, Anchor } from '@tlon/indigo-react';
import { Box, Text, Col, Anchor, Row } from '@tlon/indigo-react';
import ReactMarkdown from 'react-markdown';
import bigInt from 'big-integer';
@ -9,6 +9,7 @@ import { Comments } from '~/views/components/Comments';
import { NoteNavigation } from './NoteNavigation';
import GlobalApi from '~/logic/api/global';
import { getLatestRevision, getComments } from '~/logic/lib/publish';
import { roleForShip } from '~/logic/lib/group';
import Author from '~/views/components/Author';
import { Contacts, GraphNode, Graph, Association, Unreads, Group } from '@urbit/api';
@ -54,29 +55,37 @@ export function Note(props: NoteProps & RouteComponentProps) {
api.hark.markEachAsRead(props.association, '/',`/${index[1]}/1/1`, 'note', 'publish');
}, [props.association, props.note]);
let adminLinks: JSX.Element | null = null;
let adminLinks: JSX.Element[] = [];
const ourRole = roleForShip(group, window.ship);
if (window.ship === note?.post?.author) {
adminLinks = (
<Box display="inline-block" verticalAlign="middle">
<Link to={`${baseUrl}/edit`}>
adminLinks.push(
<Link
style={{ 'display': 'inline-block' }}
to={`${baseUrl}/edit`}
>
<Text
color="green"
color="blue"
ml={2}
>
Update
</Text>
</Link>
)
};
if (window.ship === note?.post?.author || ourRole === "admin") {
adminLinks.push(
<Text
color="red"
display='inline-block'
ml={2}
onClick={deletePost}
style={{ cursor: 'pointer' }}
>
Delete
</Text>
</Box>
);
}
)
};
const windowRef = React.useRef(null);
useEffect(() => {
@ -103,13 +112,15 @@ export function Note(props: NoteProps & RouteComponentProps) {
</Link>
<Col>
<Text display="block" mb={2}>{title || ''}</Text>
<Box display="flex">
<Row alignItems="center">
<Author
showImage
ship={post?.author}
date={post?.['time-sent']}
group={group}
/>
<Text ml={2}>{adminLinks}</Text>
</Box>
<Text ml={1}>{adminLinks}</Text>
</Row>
</Col>
<Box color="black" className="md" style={{ overflowWrap: 'break-word', overflow: 'hidden' }}>
<ReactMarkdown source={body} linkTarget={'_blank'} renderers={renderers} />

View File

@ -12,7 +12,6 @@ import {
getSnippet
} from '~/logic/lib/publish';
import { Unreads } from '@urbit/api';
import GlobalApi from '~/logic/api/global';
import ReactMarkdown from 'react-markdown';
import useHarkState from '~/logic/state/hark';
@ -21,7 +20,6 @@ interface NotePreviewProps {
book: string;
node: GraphNode;
baseUrl: string;
api: GlobalApi;
group: Group;
}
@ -96,7 +94,6 @@ export function NotePreview(props: NotePreviewProps) {
date={post?.['time-sent']}
group={group}
unread={isUnread}
api={props.api}
/>
<Box ml="auto" mr={1}>
<Link to={url}>

View File

@ -5,13 +5,11 @@ import { Col, Box, Text, Row } from '@tlon/indigo-react';
import { Contacts, Rolodex, Groups, Associations, Graph, Association, Unreads } from '@urbit/api';
import { NotebookPosts } from './NotebookPosts';
import GlobalApi from '~/logic/api/global';
import { useShowNickname } from '~/logic/lib/util';
import useContactState from '~/logic/state/contact';
import useGroupState from '~/logic/state/group';
interface NotebookProps {
api: GlobalApi;
ship: string;
book: string;
graph: Graph;
@ -40,7 +38,6 @@ export function Notebook(props: NotebookProps & RouteComponentProps): ReactEleme
const contacts = useContactState(state => state.contacts);
const contact = contacts?.[`~${ship}`];
console.log(association.resource);
const showNickname = useShowNickname(contact);
@ -61,7 +58,6 @@ export function Notebook(props: NotebookProps & RouteComponentProps): ReactEleme
host={ship}
book={book}
baseUrl={props.baseUrl}
api={props.api}
group={group}
/>
</Col>

View File

@ -11,7 +11,6 @@ interface NotebookPostsProps {
baseUrl: string;
hideAvatars?: boolean;
hideNicknames?: boolean;
api: GlobalApi;
group: Group;
}
@ -29,7 +28,6 @@ export function NotebookPosts(props: NotebookPostsProps) {
contact={contacts[`~${node.post.author}`]}
node={node}
baseUrl={props.baseUrl}
api={props.api}
group={props.group}
/>
)

View File

@ -4,8 +4,16 @@ import { Text } from '@tlon/indigo-react';
export function BackButton(props: {}) {
return (
<Link to="/~settings">
<Text display={["block", "none"]} fontSize="2" fontWeight="medium">{"<- Back to System Preferences"}</Text>
<Link to='/~settings'>
<Text
display={['block', 'none']}
fontSize='2'
fontWeight='medium'
p={4}
pb={0}
>
{'<- Back to System Preferences'}
</Text>
</Link>
);
}

View File

@ -11,8 +11,8 @@ import {
Anchor
} from '@tlon/indigo-react';
import GlobalApi from "~/logic/api/global";
import { BucketList } from "./BucketList";
import GlobalApi from '~/logic/api/global';
import { BucketList } from './BucketList';
import { S3State } from '~/types/s3-update';
import useS3State from '~/logic/state/storage';
import { BackButton } from './BackButton';
@ -33,7 +33,7 @@ interface S3FormProps {
export default function S3Form(props: S3FormProps): ReactElement {
const { api } = props;
const s3 = useStorageState(state => state.s3);
const s3 = useStorageState((state) => state.s3);
const onSubmit = useCallback(
(values: FormSchema) => {
@ -54,7 +54,8 @@ export default function S3Form(props: S3FormProps): ReactElement {
return (
<>
<Col p="5" pt="4" borderBottom="1" borderBottomColor="washedGray">
<BackButton />
<Col p='5' pt='4' borderBottom='1' borderBottomColor='washedGray'>
<Formik
initialValues={
{
@ -68,42 +69,42 @@ export default function S3Form(props: S3FormProps): ReactElement {
onSubmit={onSubmit}
>
<Form>
<BackButton/>
<Col maxWidth="600px" gapY="5">
<Col gapY="1" mt="0">
<Text color="black" fontSize={2} fontWeight="medium">
<Col maxWidth='600px' gapY='5'>
<Col gapY='1' mt='0'>
<Text color='black' fontSize={2} fontWeight='medium'>
S3 Storage Setup
</Text>
<Text gray>
Store credentials for your S3 object storage buckets on your
Urbit ship, and upload media freely to various modules.
<Anchor
target="_blank"
target='_blank'
style={{ textDecoration: 'none' }}
borderBottom="1"
ml="1"
href="https://urbit.org/using/operations/using-your-ship/#bucket-setup">
borderBottom='1'
ml='1'
href='https://urbit.org/using/operations/using-your-ship/#bucket-setup'
>
Learn more
</Anchor>
</Text>
</Col>
<Input label="Endpoint" id="s3endpoint" />
<Input label="Access Key ID" id="s3accessKeyId" />
<Input label='Endpoint' id='s3endpoint' />
<Input label='Access Key ID' id='s3accessKeyId' />
<Input
type="password"
label="Secret Access Key"
id="s3secretAccessKey"
type='password'
label='Secret Access Key'
id='s3secretAccessKey'
/>
<Button style={{ cursor: "pointer" }} type="submit">
<Button style={{ cursor: 'pointer' }} type='submit'>
Submit
</Button>
</Col>
</Form>
</Formik>
</Col>
<Col maxWidth="600px" p="5" gapY="4">
<Col gapY="1">
<Text color="black" mb={4} fontSize={2} fontWeight="medium">
<Col maxWidth='600px' p='5' gapY='4'>
<Col gapY='1'>
<Text color='black' mb={4} fontSize={2} fontWeight='medium'>
S3 Buckets
</Text>
<Text gray>

View File

@ -1,35 +1,42 @@
import React, { ReactNode } from "react";
import { useLocation } from "react-router-dom";
import Helmet from "react-helmet";
import React, { ReactNode } from 'react';
import { useLocation } from 'react-router-dom';
import Helmet from 'react-helmet';
import { Text, Box, Col, Row } from '@tlon/indigo-react';
import { NotificationPreferences } from "./components/lib/NotificationPref";
import DisplayForm from "./components/lib/DisplayForm";
import S3Form from "./components/lib/S3Form";
import { CalmPrefs } from "./components/lib/CalmPref";
import SecuritySettings from "./components/lib/Security";
import { LeapSettings } from "./components/lib/LeapSettings";
import { useHashLink } from "~/logic/lib/useHashLink";
import { SidebarItem as BaseSidebarItem } from "~/views/landscape/components/SidebarItem";
import { PropFunc } from "~/types";
import { NotificationPreferences } from './components/lib/NotificationPref';
import DisplayForm from './components/lib/DisplayForm';
import S3Form from './components/lib/S3Form';
import { CalmPrefs } from './components/lib/CalmPref';
import SecuritySettings from './components/lib/Security';
import { LeapSettings } from './components/lib/LeapSettings';
import { useHashLink } from '~/logic/lib/useHashLink';
import { SidebarItem as BaseSidebarItem } from '~/views/landscape/components/SidebarItem';
import { PropFunc } from '~/types';
export const Skeleton = (props: { children: ReactNode }) => (
<Box height="100%" width="100%" px={[0, 3]} pb={[0, 3]} borderRadius={1}>
<Box height='100%' width='100%' px={[0, 3]} pb={[0, 3]} borderRadius={1}>
<Box
height="100%"
width="100%"
borderRadius={1}
bg="white"
display='grid'
gridTemplateColumns={[
'100%',
'minmax(150px, 1fr) 3fr',
'minmax(250px, 1fr) 4fr'
]}
gridTemplateRows='100%'
height='100%'
width='100%'
borderRadius={2}
bg='white'
border={1}
borderColor="washedGray"
borderColor='washedGray'
>
{props.children}
</Box>
</Box>
);
type ProvSideProps = "to" | "selected";
type ProvSideProps = 'to' | 'selected';
type BaseProps = PropFunc<typeof BaseSidebarItem>;
function SidebarItem(props: { hash: string } & Omit<BaseProps, ProvSideProps>) {
const { hash, icon, text, ...rest } = props;
@ -54,16 +61,15 @@ function SettingsItem(props: { children: ReactNode }) {
const { children } = props;
return (
<Box borderBottom="1" borderBottomColor="washedGray">
<Box borderBottom='1' borderBottomColor='washedGray'>
{children}
</Box>
);
}
export default function SettingsScreen(props: any) {
const location = useLocation();
const hash = location.hash.slice(1)
const hash = location.hash.slice(1);
return (
<>
@ -71,68 +77,49 @@ export default function SettingsScreen(props: any) {
<title>Landscape - Settings</title>
</Helmet>
<Skeleton>
<Row height="100%" overflow="hidden">
<Col
height="100%"
borderRight="1"
borderRightColor="washedGray"
display={hash === "" ? "flex" : ["none", "flex"]}
minWidth="250px"
width="100%"
maxWidth={["100vw", "350px"]}
>
<Text
display="block"
my="4"
mx="3"
fontSize="2"
fontWeight="medium"
height='100%'
borderRight='1'
borderRightColor='washedGray'
display={hash === '' ? 'flex' : ['none', 'flex']}
width='100%'
overflowY='auto'
>
<Text display='block' mt='4' mb='3' mx='3' fontSize='2' fontWeight='700'>
System Preferences
</Text>
<Col gapY="1">
<Col>
<SidebarItem
icon="Inbox"
text="Notifications"
hash="notifications"
icon='Inbox'
text='Notifications'
hash='notifications'
/>
<SidebarItem icon="Image" text="Display" hash="display" />
<SidebarItem icon="Upload" text="Remote Storage" hash="s3" />
<SidebarItem icon="LeapArrow" text="Leap" hash="leap" />
<SidebarItem icon="Node" text="CalmEngine" hash="calm" />
<SidebarItem icon='Image' text='Display' hash='display' />
<SidebarItem icon='Upload' text='Remote Storage' hash='s3' />
<SidebarItem icon='LeapArrow' text='Leap' hash='leap' />
<SidebarItem icon='Node' text='CalmEngine' hash='calm' />
<SidebarItem
icon="Locked"
text="Devices + Security"
hash="security"
icon='Locked'
text='Devices + Security'
hash='security'
/>
</Col>
</Col>
<Col flexGrow={1} overflowY="auto">
<Col flexGrow={1} overflowY='auto'>
<SettingsItem>
{hash === "notifications" && (
{hash === 'notifications' && (
<NotificationPreferences
{...props}
graphConfig={props.notificationsGraphConfig}
/>
)}
{hash === "display" && (
<DisplayForm api={props.api} />
)}
{hash === "s3" && (
<S3Form api={props.api} />
)}
{hash === "leap" && (
<LeapSettings api={props.api} />
)}
{hash === "calm" && (
<CalmPrefs api={props.api} />
)}
{hash === "security" && (
<SecuritySettings api={props.api} />
)}
{hash === 'display' && <DisplayForm api={props.api} />}
{hash === 's3' && <S3Form api={props.api} />}
{hash === 'leap' && <LeapSettings api={props.api} />}
{hash === 'calm' && <CalmPrefs api={props.api} />}
{hash === 'security' && <SecuritySettings api={props.api} />}
</SettingsItem>
</Col>
</Row>
</Skeleton>
</>
);

View File

@ -11,7 +11,6 @@ import useSettingsState, {selectCalmState} from "~/logic/state/settings";
import useLocalState from "~/logic/state/local";
import OverlaySigil from './OverlaySigil';
import { Sigil } from '~/logic/lib/sigil';
import GlobalApi from '~/logic/api/global';
import Timestamp from './Timestamp';
import useContactState from '~/logic/state/contact';
@ -22,7 +21,6 @@ interface AuthorProps {
children?: ReactNode;
unread?: boolean;
group: Group;
api?: GlobalApi;
}
// eslint-disable-next-line max-lines-per-function

View File

@ -10,6 +10,7 @@ import { Group } from '@urbit/api';
import GlobalApi from '~/logic/api/global';
import Author from '~/views/components/Author';
import { MentionText } from '~/views/components/MentionText';
import { roleForShip } from '~/logic/lib/group';
import { getLatestCommentRevision } from '~/logic/lib/publish';
const ClickBox = styled(Box)`
@ -31,7 +32,7 @@ interface CommentItemProps {
export function CommentItem(props: CommentItemProps): ReactElement {
const { ship, name, api, comment, group } = props;
const [, post] = getLatestCommentRevision(comment);
const disabled = props.pending || window.ship !== post?.author;
const disabled = props.pending;
const onDelete = async () => {
await api.graph.removeNodes(ship, name, [comment.post?.index]);
@ -41,6 +42,29 @@ export function CommentItem(props: CommentItemProps): ReactElement {
const commentIndex = commentIndexArray[commentIndexArray.length - 1];
const updateUrl = `${props.baseUrl}/${commentIndex}`;
const adminLinks: JSX.Element[] = [];
const ourRole = roleForShip(group, window.ship);
if (window.ship == post?.author && !disabled) {
adminLinks.push(
<Link to={updateUrl}>
<Text
color="blue"
ml={2}
>
Update
</Text>
</Link>
)
};
if ((window.ship == post?.author || ourRole == "admin") && !disabled) {
adminLinks.push(
<ClickBox display="inline-block" color="red" onClick={onDelete}>
<Text color='red'>Delete</Text>
</ClickBox>
)
};
return (
<Box mb={4} opacity={post?.pending ? '60%' : '100%'}>
<Row bg="white" my={3}>
@ -50,23 +74,10 @@ export function CommentItem(props: CommentItemProps): ReactElement {
date={post?.['time-sent']}
unread={props.unread}
group={group}
api={api}
>
{!disabled && (
<Box display="inline-block" verticalAlign="middle">
<Link to={updateUrl}>
<Text
color="green"
ml={2}
>
Update
</Text>
</Link>
<ClickBox display="inline-block" color="red" onClick={onDelete}>
<Text color='red'>Delete</Text>
</ClickBox>
</Box>
)}
<Row alignItems="center">
{adminLinks}
</Row>
</Author>
</Row>
<Box mb={2}>

View File

@ -51,7 +51,6 @@ class RemoteContent extends Component<RemoteContentProps, RemoteContentState> {
}
save = () => {
console.log(`saving for: ${this.props.url}`);
if(this.saving) {
return;
}
@ -60,7 +59,6 @@ class RemoteContent extends Component<RemoteContentProps, RemoteContentState> {
};
restore = () => {
console.log(`restoring for: ${this.props.url}`);
this.saving = false;
this.props.restore();
}

View File

@ -9,6 +9,7 @@ import {
Button,
BaseImage
} from '@tlon/indigo-react';
import ReconnectButton from './ReconnectButton';
import { Dropdown } from './Dropdown';
import { StatusBarItem } from './StatusBarItem';
@ -19,6 +20,7 @@ import { useTutorialModal } from './useTutorialModal';
import useHarkState from '~/logic/state/hark';
import useInviteState from '~/logic/state/invite';
import useContactState from '~/logic/state/contact';
import { useHistory } from 'react-router-dom';
import useLocalState, { selectLocalState } from '~/logic/state/local';
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
@ -26,8 +28,9 @@ import useSettingsState, { selectCalmState } from '~/logic/state/settings';
const localSel = selectLocalState(['toggleOmnibox']);
const StatusBar = (props) => {
const { ourContact, api, ship } = props;
const { api, ship } = props;
const history = useHistory();
const ourContact = useContactState((state) => state.contacts[`~${ship}`]);
const notificationsCount = useHarkState((state) => state.notificationsCount);
const doNotDisturb = useHarkState((state) => state.doNotDisturb);
const inviteState = useInviteState((state) => state.invites);
@ -38,7 +41,7 @@ const StatusBar = (props) => {
const { toggleOmnibox } = useLocalState(localSel);
const { hideAvatars } = useSettingsState(selectCalmState);
const color = !!ourContact ? `#${uxToHex(props.ourContact.color)}` : '#000';
const color = !!ourContact ? `#${uxToHex(ourContact.color)}` : '#000';
const xPadding = !hideAvatars && ourContact?.avatar ? '0' : '2';
const bgColor = !hideAvatars && ourContact?.avatar ? '' : color;
const profileImage =

View File

@ -9,15 +9,13 @@ import {
StatelessAsyncButton as AsyncButton,
StatelessAsyncButton
} from './StatelessAsyncButton';
import { Notebooks, Graphs, Inbox } from '@urbit/api';
import { Graphs } from '@urbit/api';
import useGraphState from '~/logic/state/graph';
interface UnjoinedResourceProps {
association: Association;
api: GlobalApi;
baseUrl: string;
notebooks: Notebooks;
inbox: Inbox;
}
function isJoined(path: string) {
@ -31,7 +29,7 @@ function isJoined(path: string) {
}
export function UnjoinedResource(props: UnjoinedResourceProps) {
const { api, notebooks, inbox } = props;
const { api } = props;
const history = useHistory();
const rid = props.association.resource;
const appName = props.association['app-name'];
@ -52,7 +50,7 @@ export function UnjoinedResource(props: UnjoinedResourceProps) {
if (isJoined(rid)({ graphKeys })) {
history.push(`${props.baseUrl}/resource/${app}${rid}`);
}
}, [props.association, inbox, graphKeys, notebooks]);
}, [props.association, graphKeys]);
return (
<Center p={6}>

View File

@ -493,7 +493,7 @@ export default class VirtualScroller<T> extends Component<VirtualScrollerProps<T
<>
{!IS_IOS && (<Box borderRadius="3" top ={isTop ? "0" : undefined} bottom={!isTop ? "0" : undefined} ref={el => { this.scrollRef = el; }} right="0" height="50px" position="absolute" width="4px" backgroundColor="lightGray" />)}
<ScrollbarLessBox overflowY='scroll' ref={this.setWindow} onScroll={this.onScroll} style={{ ...style, ...{ transform }, "-webkit-overflow-scrolling": "auto" }}>
<ScrollbarLessBox overflowY='scroll' ref={this.setWindow} onScroll={this.onScroll} style={{ ...style, ...{ transform }, "WebkitOverflowScrolling": "auto" }}>
<Box style={{ transform, width: 'calc(100% - 4px)' }}>
{(isTop ? !atStart : !atEnd) && (<Center height="5">
<LoadingSpinner />

View File

@ -16,9 +16,7 @@ export function useTutorialModal(
setTutorialRef(anchorRef.current);
}
return () => {
console.log(tutorialProgress);
}
return () => {}
}, [tutorialProgress, show, anchorRef]);
return show && onProgress === tutorialProgress;

View File

@ -10,16 +10,14 @@
font-family: 'Inter';
font-style: normal;
font-weight: 500;
src: url("/~landscape/fonts/inter-medium.woff2") format("woff2"),
url("https://media.urbit.org/fonts/Inter-Medium.woff2") format("woff2");
src: url("https://media.urbit.org/fonts/Inter-Medium.woff2") format("woff2");
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
src: url("/~landscape/fonts/inter-semibold.woff2") format("woff2"),
url("https://media.urbit.org/fonts/Inter-SemiBold.woff2") format("woff2");
src: url("https://media.urbit.org/fonts/Inter-SemiBold.woff2") format("woff2");
}
@font-face {

View File

@ -158,8 +158,6 @@ export function GroupsPane(props: GroupsPaneProps) {
baseUrl={baseUrl}
>
<UnjoinedResource
notebooks={props.notebooks}
inbox={props.inbox}
baseUrl={baseUrl}
api={api}
association={association}
@ -191,9 +189,8 @@ export function GroupsPane(props: GroupsPaneProps) {
<Route
path={relativePath('')}
render={(routeProps) => {
const hasDescription = groupAssociation?.metadata?.description;
const channelCount = Object.keys(props?.associations?.graph ?? {}).filter((e) => {
return props?.associations?.graph?.[e]?.['group'] === groupPath;
const channelCount = Object.keys(associations?.graph ?? {}).filter((e) => {
return associations?.graph?.[e]?.['group'] === groupPath;
}).length;
let summary: ReactNode;
if(groupAssociation?.group) {

View File

@ -15,7 +15,7 @@ export function MetadataIcon(props: MetadataIconProps) {
const bgColor = metadata.picture ? {} : { bg: `#${uxToHex(metadata.color)}` };
return (
<Box {...bgColor} {...rest} borderRadius={2} boxShadow="inset 0 0 0 1px" color="lightGray" overflow="hidden">
<Box {...bgColor} {...rest} borderRadius={1} boxShadow="inset 0 0 0 1px" color="lightGray" overflow="hidden">
{metadata.picture && <Image height="100%" src={metadata.picture} />}
</Box>
);

View File

@ -37,7 +37,6 @@ interface SidebarProps {
api: GlobalApi;
selected?: string;
selectedGroup?: string;
includeUnmanaged?: boolean;
apps: SidebarAppConfigs;
baseUrl: string;
mobileHide?: boolean;

View File

@ -30,7 +30,7 @@ export const SidebarItem = ({
bgActive="washedGray"
display="flex"
px="3"
py="1"
py="2"
justifyContent="space-between"
{...rest}
>

View File

@ -5,7 +5,6 @@ import { Associations } from '@urbit/api/metadata';
import { Sidebar } from './Sidebar/Sidebar';
import GlobalApi from '~/logic/api/global';
import GlobalSubscription from '~/logic/subscription/global';
import { useGraphModule } from './Sidebar/Apps';
import { Body } from '~/views/components/Body';
import { Workspace } from '~/types/workspace';
@ -16,14 +15,11 @@ import ErrorBoundary from '~/views/components/ErrorBoundary';
interface SkeletonProps {
children: ReactNode;
recentGroups: string[];
linkListening: Set<Path>;
selected?: string;
selectedApp?: AppName;
baseUrl: string;
mobileHide?: boolean;
api: GlobalApi;
subscription: GlobalSubscription;
includeUnmanaged: boolean;
workspace: Workspace;
}

View File

@ -0,0 +1,8 @@
import React from 'react';
if (process.env.NODE_ENV === 'development') {
const whyDidYouRender = require('@welldone-software/why-did-you-render');
whyDidYouRender(React, {
trackAllPureComponents: true,
});
}