mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-14 17:41:33 +03:00
Merge branch 'release/next-js' into release/next-userspace
This commit is contained in:
commit
dfe186b96e
@ -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',
|
||||
|
9
pkg/interface/package-lock.json
generated
9
pkg/interface/package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -1,3 +1,4 @@
|
||||
import './wdyr';
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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'
|
||||
]);
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
));
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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}`}
|
||||
|
@ -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}>
|
||||
|
@ -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>
|
||||
|
@ -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%'
|
||||
|
@ -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 : ''}
|
||||
|
@ -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} />
|
||||
|
@ -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}>
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
/>
|
||||
)
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
@ -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
|
||||
|
@ -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}>
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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 =
|
||||
|
@ -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}>
|
||||
|
@ -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 />
|
||||
|
@ -16,9 +16,7 @@ export function useTutorialModal(
|
||||
setTutorialRef(anchorRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
console.log(tutorialProgress);
|
||||
}
|
||||
return () => {}
|
||||
}, [tutorialProgress, show, anchorRef]);
|
||||
|
||||
return show && onProgress === tutorialProgress;
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -37,7 +37,6 @@ interface SidebarProps {
|
||||
api: GlobalApi;
|
||||
selected?: string;
|
||||
selectedGroup?: string;
|
||||
includeUnmanaged?: boolean;
|
||||
apps: SidebarAppConfigs;
|
||||
baseUrl: string;
|
||||
mobileHide?: boolean;
|
||||
|
@ -30,7 +30,7 @@ export const SidebarItem = ({
|
||||
bgActive="washedGray"
|
||||
display="flex"
|
||||
px="3"
|
||||
py="1"
|
||||
py="2"
|
||||
justifyContent="space-between"
|
||||
{...rest}
|
||||
>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
8
pkg/interface/src/wdyr.js
Normal file
8
pkg/interface/src/wdyr.js
Normal 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,
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user