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: {
|
use: {
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
options: {
|
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: [
|
plugins: [
|
||||||
'@babel/transform-runtime',
|
'@babel/transform-runtime',
|
||||||
'@babel/plugin-proposal-object-rest-spread',
|
'@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"
|
"@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": {
|
"@xtuc/ieee754": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
||||||
|
@ -71,6 +71,7 @@
|
|||||||
"@types/yup": "^0.29.11",
|
"@types/yup": "^0.29.11",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.15.0",
|
"@typescript-eslint/eslint-plugin": "^4.15.0",
|
||||||
"@urbit/eslint-config": "file:../npm/eslint-config",
|
"@urbit/eslint-config": "file:../npm/eslint-config",
|
||||||
|
"@welldone-software/why-did-you-render": "^6.1.0",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"babel-loader": "^8.2.2",
|
"babel-loader": "^8.2.2",
|
||||||
"babel-plugin-lodash": "^3.3.4",
|
"babel-plugin-lodash": "^3.3.4",
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import './wdyr';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as ReactDOM from 'react-dom';
|
import * as ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
@ -77,7 +77,6 @@ export default class MetadataApi extends BaseApi<StoreState> {
|
|||||||
tempChannel.delete();
|
tempChannel.delete();
|
||||||
},
|
},
|
||||||
(ev: any) => {
|
(ev: any) => {
|
||||||
console.log(ev);
|
|
||||||
if ('metadata-hook-update' in ev) {
|
if ('metadata-hook-update' in ev) {
|
||||||
done = true;
|
done = true;
|
||||||
tempChannel.delete();
|
tempChannel.delete();
|
||||||
|
@ -23,9 +23,10 @@ export const Sigil = memo(
|
|||||||
size,
|
size,
|
||||||
svgClass = '',
|
svgClass = '',
|
||||||
icon = false,
|
icon = false,
|
||||||
padding = 0
|
padding = 0,
|
||||||
|
display = 'inline-block'
|
||||||
}) => {
|
}) => {
|
||||||
const innerSize = Number(size) - 2*padding;
|
const innerSize = Number(size) - 2 * padding;
|
||||||
const paddingPx = `${padding}px`;
|
const paddingPx = `${padding}px`;
|
||||||
const foregroundColor = foreground
|
const foregroundColor = foreground
|
||||||
? foreground
|
? foreground
|
||||||
@ -34,14 +35,14 @@ export const Sigil = memo(
|
|||||||
<Box
|
<Box
|
||||||
backgroundColor={color}
|
backgroundColor={color}
|
||||||
borderRadius={icon ? '1' : '0'}
|
borderRadius={icon ? '1' : '0'}
|
||||||
display='inline-block'
|
display={display}
|
||||||
height={size}
|
height={size}
|
||||||
width={size}
|
width={size}
|
||||||
className={classes}
|
className={classes}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Box
|
<Box
|
||||||
display='inline-block'
|
display={display}
|
||||||
borderRadius={icon ? '1' : '0'}
|
borderRadius={icon ? '1' : '0'}
|
||||||
flexBasis={size}
|
flexBasis={size}
|
||||||
backgroundColor={color}
|
backgroundColor={color}
|
||||||
|
@ -133,7 +133,7 @@ function graphWatchSelf(json: any, state: HarkState): HarkState {
|
|||||||
function readAll(json: any, state: HarkState): HarkState {
|
function readAll(json: any, state: HarkState): HarkState {
|
||||||
const data = _.get(json, 'read-all');
|
const data = _.get(json, 'read-all');
|
||||||
if(data) {
|
if(data) {
|
||||||
clearState(state);
|
state = clearState(state);
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@ -149,15 +149,15 @@ function removeGraph(json: any, state: HarkState): HarkState {
|
|||||||
function seenIndex(json: any, state: HarkState): HarkState {
|
function seenIndex(json: any, state: HarkState): HarkState {
|
||||||
const data = _.get(json, 'seen-index');
|
const data = _.get(json, 'seen-index');
|
||||||
if(data) {
|
if(data) {
|
||||||
updateNotificationStats(state, data.index, 'last', () => data.time);
|
state = updateNotificationStats(state, data.index, 'last', () => data.time);
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
function readEach(json: any, state: HarkState): HarkState {
|
function readEach(json: any, state: HarkState): HarkState {
|
||||||
const data = _.get(json, 'read-each');
|
const data = _.get(json, 'read-each');
|
||||||
if(data) {
|
if (data) {
|
||||||
updateUnreads(state, data.index, u => u.delete(data.target));
|
state = updateUnreads(state, data.index, u => u.delete(data.target));
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@ -165,7 +165,7 @@ function readEach(json: any, state: HarkState): HarkState {
|
|||||||
function readSince(json: any, state: HarkState): HarkState {
|
function readSince(json: any, state: HarkState): HarkState {
|
||||||
const data = _.get(json, 'read-count');
|
const data = _.get(json, 'read-count');
|
||||||
if(data) {
|
if(data) {
|
||||||
updateUnreadCount(state, data, () => 0);
|
state = updateUnreadCount(state, data, () => 0);
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@ -173,7 +173,7 @@ function readSince(json: any, state: HarkState): HarkState {
|
|||||||
function unreadSince(json: any, state: HarkState): HarkState {
|
function unreadSince(json: any, state: HarkState): HarkState {
|
||||||
const data = _.get(json, 'unread-count');
|
const data = _.get(json, 'unread-count');
|
||||||
if(data) {
|
if(data) {
|
||||||
updateUnreadCount(state, data.index, u => u + 1);
|
state = updateUnreadCount(state, data.index, u => u + 1);
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@ -181,7 +181,7 @@ function unreadSince(json: any, state: HarkState): HarkState {
|
|||||||
function unreadEach(json: any, state: HarkState): HarkState {
|
function unreadEach(json: any, state: HarkState): HarkState {
|
||||||
const data = _.get(json, 'unread-each');
|
const data = _.get(json, 'unread-each');
|
||||||
if(data) {
|
if(data) {
|
||||||
updateUnreads(state, data.index, us => us.add(data.target));
|
state = updateUnreads(state, data.index, us => us.add(data.target));
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@ -191,13 +191,14 @@ function unreads(json: any, state: HarkState): HarkState {
|
|||||||
if(data) {
|
if(data) {
|
||||||
data.forEach(({ index, stats }) => {
|
data.forEach(({ index, stats }) => {
|
||||||
const { unreads, notifications, last } = stats;
|
const { unreads, notifications, last } = stats;
|
||||||
updateNotificationStats(state, index, 'notifications', x => x + notifications);
|
state = updateNotificationStats(state, index, 'notifications', x => x + notifications);
|
||||||
updateNotificationStats(state, index, 'last', () => last);
|
state = updateNotificationStats(state, index, 'last', () => last);
|
||||||
if('count' in unreads) {
|
if('count' in unreads) {
|
||||||
updateUnreadCount(state, index, (u = 0) => u + unreads.count);
|
state = updateUnreadCount(state, index, (u = 0) => u + unreads.count);
|
||||||
} else {
|
} else {
|
||||||
|
state = updateUnreads(state, index, s => new Set());
|
||||||
unreads.each.forEach((u: string) => {
|
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;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearState(state: HarkState) {
|
function clearState(state: HarkState): HarkState {
|
||||||
const initialState = {
|
const initialState = {
|
||||||
notifications: new BigIntOrderedMap<Timebox>(),
|
notifications: new BigIntOrderedMap<Timebox>(),
|
||||||
archivedNotifications: new BigIntOrderedMap<Timebox>(),
|
archivedNotifications: new BigIntOrderedMap<Timebox>(),
|
||||||
@ -225,6 +226,7 @@ function clearState(state: HarkState) {
|
|||||||
Object.keys(initialState).forEach((key) => {
|
Object.keys(initialState).forEach((key) => {
|
||||||
state[key] = initialState[key];
|
state[key] = initialState[key];
|
||||||
});
|
});
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateUnreadCount(state: HarkState, index: NotifIndex, count: (c: number) => number): HarkState {
|
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)) {
|
if(!('graph' in index)) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
const unreads = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], new Set<string>());
|
let unreads = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], new Set<string>());
|
||||||
const oldSize = unreads.size;
|
|
||||||
f(unreads);
|
f(unreads);
|
||||||
const newSize = unreads.size;
|
|
||||||
_.set(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], unreads);
|
_.set(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], unreads);
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@ -254,7 +255,7 @@ function updateNotificationStats(state: HarkState, index: NotifIndex, statField:
|
|||||||
if(statField === 'notifications') {
|
if(statField === 'notifications') {
|
||||||
state.notificationsCount = f(state.notificationsCount);
|
state.notificationsCount = f(state.notificationsCount);
|
||||||
}
|
}
|
||||||
if('graph' in index) {
|
if ('graph' in index) {
|
||||||
const curr = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, statField], 0);
|
const curr = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, statField], 0);
|
||||||
_.set(state.unreads.graph, [index.graph.graph, index.graph.index, statField], f(curr));
|
_.set(state.unreads.graph, [index.graph.graph, index.graph.index, statField], f(curr));
|
||||||
} else if('group' in index) {
|
} else if('group' in index) {
|
||||||
@ -276,7 +277,6 @@ function added(json: any, state: HarkState): HarkState {
|
|||||||
);
|
);
|
||||||
if (arrIdx !== -1) {
|
if (arrIdx !== -1) {
|
||||||
if (timebox[arrIdx]?.notification?.read) {
|
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);
|
state = updateNotificationStats(state, index, 'notifications', x => x+1);
|
||||||
}
|
}
|
||||||
timebox[arrIdx] = { index, notification };
|
timebox[arrIdx] = { index, notification };
|
||||||
@ -361,7 +361,7 @@ function read(json: any, state: HarkState): HarkState {
|
|||||||
const data = _.get(json, 'read-note', false);
|
const data = _.get(json, 'read-note', false);
|
||||||
if (data) {
|
if (data) {
|
||||||
const { time, index } = 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);
|
setRead(time, index, true, state);
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
@ -371,7 +371,7 @@ function unread(json: any, state: HarkState): HarkState {
|
|||||||
const data = _.get(json, 'unread-note', false);
|
const data = _.get(json, 'unread-note', false);
|
||||||
if (data) {
|
if (data) {
|
||||||
const { time, index } = 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);
|
setRead(time, index, false, state);
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
@ -397,7 +397,7 @@ function archive(json: any, state: HarkState): HarkState {
|
|||||||
state.notifications.set(time, unarchived);
|
state.notifications.set(time, unarchived);
|
||||||
}
|
}
|
||||||
const newlyRead = archived.filter(x => !x.notification.read).length;
|
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;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ export const reduceState = <
|
|||||||
export let stateStorageKeys: string[] = [];
|
export let stateStorageKeys: string[] = [];
|
||||||
|
|
||||||
export const stateStorageKey = (stateName: string) => {
|
export const stateStorageKey = (stateName: string) => {
|
||||||
stateName = `Landcape${stateName}State`;
|
stateName = `Landscape${stateName}State`;
|
||||||
stateStorageKeys = [...new Set([...stateStorageKeys, stateName])];
|
stateStorageKeys = [...new Set([...stateStorageKeys, stateName])];
|
||||||
return stateName;
|
return stateName;
|
||||||
};
|
};
|
||||||
|
@ -24,7 +24,7 @@ type ChatInputProps = IuseStorage & {
|
|||||||
message: string;
|
message: string;
|
||||||
deleteMessage(): void;
|
deleteMessage(): void;
|
||||||
hideAvatars: boolean;
|
hideAvatars: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
interface ChatInputState {
|
interface ChatInputState {
|
||||||
inCodeMode: boolean;
|
inCodeMode: boolean;
|
||||||
@ -60,20 +60,23 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
|||||||
|
|
||||||
submit(text) {
|
submit(text) {
|
||||||
const { props, state } = this;
|
const { props, state } = this;
|
||||||
const [,,ship,name] = props.station.split('/');
|
const [, , ship, name] = props.station.split('/');
|
||||||
if (state.inCodeMode) {
|
if (state.inCodeMode) {
|
||||||
this.setState({
|
this.setState(
|
||||||
inCodeMode: false
|
{
|
||||||
}, async () => {
|
inCodeMode: false
|
||||||
const output = await props.api.graph.eval(text);
|
},
|
||||||
const contents: Content[] = [{ code: { output, expression: text } }];
|
async () => {
|
||||||
const post = createPost(contents);
|
const output = await props.api.graph.eval(text);
|
||||||
props.api.graph.addPost(ship, name, post);
|
const contents: Content[] = [{ code: { output, expression: text } }];
|
||||||
});
|
const post = createPost(contents);
|
||||||
|
props.api.graph.addPost(ship, name, post);
|
||||||
|
}
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const post = createPost(tokenizeMessage((text)));
|
const post = createPost(tokenizeMessage(text));
|
||||||
|
|
||||||
props.deleteMessage();
|
props.deleteMessage();
|
||||||
|
|
||||||
@ -86,8 +89,8 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
|||||||
this.chatEditor.current.editor.setValue(url);
|
this.chatEditor.current.editor.setValue(url);
|
||||||
this.setState({ uploadingPaste: false });
|
this.setState({ uploadingPaste: false });
|
||||||
} else {
|
} else {
|
||||||
const [,,ship,name] = props.station.split('/');
|
const [, , ship, name] = props.station.split('/');
|
||||||
props.api.graph.addPost(ship,name, createPost([{ url }]));
|
props.api.graph.addPost(ship, name, createPost([{ url }]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +113,8 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Array.from(files).forEach((file) => {
|
Array.from(files).forEach((file) => {
|
||||||
this.props.uploadDefault(file)
|
this.props
|
||||||
|
.uploadDefault(file)
|
||||||
.then(this.uploadSuccess)
|
.then(this.uploadSuccess)
|
||||||
.catch(this.uploadError);
|
.catch(this.uploadError);
|
||||||
});
|
});
|
||||||
@ -119,32 +123,40 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
|||||||
render() {
|
render() {
|
||||||
const { props, state } = this;
|
const { props, state } = this;
|
||||||
|
|
||||||
const color = props.ourContact
|
const color = props.ourContact ? uxToHex(props.ourContact.color) : '000000';
|
||||||
? uxToHex(props.ourContact.color) : '000000';
|
|
||||||
|
|
||||||
const sigilClass = props.ourContact
|
const sigilClass = props.ourContact ? '' : 'mix-blend-diff';
|
||||||
? '' : 'mix-blend-diff';
|
|
||||||
|
|
||||||
const avatar = (
|
const avatar =
|
||||||
props.ourContact &&
|
props.ourContact && props.ourContact?.avatar && !props.hideAvatars ? (
|
||||||
((props.ourContact?.avatar) && !props.hideAvatars)
|
<BaseImage
|
||||||
)
|
|
||||||
? <BaseImage
|
|
||||||
src={props.ourContact.avatar}
|
src={props.ourContact.avatar}
|
||||||
height={16}
|
height={24}
|
||||||
width={16}
|
width={24}
|
||||||
style={{ objectFit: 'cover' }}
|
style={{ objectFit: 'cover' }}
|
||||||
borderRadius={1}
|
borderRadius={1}
|
||||||
display='inline-block'
|
display='inline-block'
|
||||||
/>
|
/>
|
||||||
: <Sigil
|
) : (
|
||||||
ship={window.ship}
|
<Box
|
||||||
size={16}
|
width={24}
|
||||||
color={`#${color}`}
|
height={24}
|
||||||
classes={sigilClass}
|
display='flex'
|
||||||
icon
|
justifyContent='center'
|
||||||
padding={2}
|
alignItems='center'
|
||||||
/>;
|
backgroundColor={`#${color}`}
|
||||||
|
borderRadius={1}
|
||||||
|
>
|
||||||
|
<Sigil
|
||||||
|
ship={window.ship}
|
||||||
|
size={16}
|
||||||
|
color={`#${color}`}
|
||||||
|
classes={sigilClass}
|
||||||
|
icon
|
||||||
|
padding={2}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row
|
<Row
|
||||||
@ -158,7 +170,7 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
|||||||
className='cf'
|
className='cf'
|
||||||
zIndex={0}
|
zIndex={0}
|
||||||
>
|
>
|
||||||
<Row p='2' alignItems='center'>
|
<Row p='12px 4px 12px 12px' alignItems='center'>
|
||||||
{avatar}
|
{avatar}
|
||||||
</Row>
|
</Row>
|
||||||
<ChatEditor
|
<ChatEditor
|
||||||
@ -170,31 +182,23 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
|||||||
onPaste={this.onPaste.bind(this)}
|
onPaste={this.onPaste.bind(this)}
|
||||||
placeholder='Message...'
|
placeholder='Message...'
|
||||||
/>
|
/>
|
||||||
<Box
|
<Box mx={2} flexShrink={0} height='16px' width='16px' flexBasis='16px'>
|
||||||
mx={2}
|
{this.props.canUpload ? (
|
||||||
flexShrink={0}
|
this.props.uploading ? (
|
||||||
height='16px'
|
<LoadingSpinner />
|
||||||
width='16px'
|
) : (
|
||||||
flexBasis='16px'
|
<Icon
|
||||||
>
|
icon='Links'
|
||||||
{this.props.canUpload
|
width='16'
|
||||||
? this.props.uploading
|
height='16'
|
||||||
? <LoadingSpinner />
|
onClick={() =>
|
||||||
: <Icon icon='Links'
|
this.props.promptUpload().then(this.uploadSuccess)
|
||||||
width="16"
|
}
|
||||||
height="16"
|
/>
|
||||||
onClick={() => this.props.promptUpload().then(this.uploadSuccess)}
|
)
|
||||||
/>
|
) : null}
|
||||||
: null
|
|
||||||
}
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box mr={2} flexShrink={0} height='16px' width='16px' flexBasis='16px'>
|
||||||
mr={2}
|
|
||||||
flexShrink={0}
|
|
||||||
height='16px'
|
|
||||||
width='16px'
|
|
||||||
flexBasis='16px'
|
|
||||||
>
|
|
||||||
<Icon
|
<Icon
|
||||||
icon='Dojo'
|
icon='Dojo'
|
||||||
onClick={this.toggleCode}
|
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 moment from 'moment';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import VisibilitySensor from 'react-visibility-sensor';
|
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 { Sigil } from '~/logic/lib/sigil';
|
||||||
import OverlaySigil from '~/views/components/OverlaySigil';
|
import OverlaySigil from '~/views/components/OverlaySigil';
|
||||||
import {
|
import {
|
||||||
@ -33,12 +33,13 @@ import TextContent from './content/text';
|
|||||||
import CodeContent from './content/code';
|
import CodeContent from './content/code';
|
||||||
import RemoteContent from '~/views/components/RemoteContent';
|
import RemoteContent from '~/views/components/RemoteContent';
|
||||||
import { Mention } from '~/views/components/MentionText';
|
import { Mention } from '~/views/components/MentionText';
|
||||||
|
import { Dropdown } from '~/views/components/Dropdown';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import useLocalState from '~/logic/state/local';
|
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 Timestamp from '~/views/components/Timestamp';
|
||||||
import useContactState from '~/logic/state/contact';
|
import useContactState from '~/logic/state/contact';
|
||||||
import {useIdlingState} from '~/logic/lib/idling';
|
import { useIdlingState } from '~/logic/lib/idling';
|
||||||
|
|
||||||
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
||||||
|
|
||||||
@ -64,39 +65,156 @@ export const DayBreak = ({ when, shimTop = false }: DayBreakProps) => (
|
|||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const UnreadMarker = React.forwardRef(({ dayBreak, when, api, association }, ref) => {
|
export const UnreadMarker = React.forwardRef(
|
||||||
const [visible, setVisible] = useState(false);
|
({ dayBreak, when, api, association }, ref) => {
|
||||||
const idling = useIdlingState();
|
const [visible, setVisible] = useState(false);
|
||||||
const dismiss = useCallback(() => {
|
const idling = useIdlingState();
|
||||||
api.hark.markCountAsRead(association, '/', 'message');
|
const dismiss = useCallback(() => {
|
||||||
}, [api, association]);
|
api.hark.markCountAsRead(association, '/', 'message');
|
||||||
|
}, [api, association]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(visible && !idling) {
|
if (visible && !idling) {
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
}, [visible, idling]);
|
}, [visible, idling]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row
|
||||||
|
position='absolute'
|
||||||
|
ref={ref}
|
||||||
|
px={2}
|
||||||
|
mt={2}
|
||||||
|
height={5}
|
||||||
|
justifyContent='center'
|
||||||
|
alignItems='center'
|
||||||
|
width='100%'
|
||||||
|
>
|
||||||
|
<Rule borderColor='lightBlue' />
|
||||||
|
<VisibilitySensor onChange={setVisible}>
|
||||||
|
<Text color='blue' fontSize={0} flexShrink='0' px={2}>
|
||||||
|
New messages below
|
||||||
|
</Text>
|
||||||
|
</VisibilitySensor>
|
||||||
|
<Rule borderColor='lightBlue' />
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const MessageActionItem = (props) => {
|
||||||
return (
|
return (
|
||||||
<Row
|
<Row
|
||||||
position='absolute'
|
color='black'
|
||||||
ref={ref}
|
cursor='pointer'
|
||||||
px={2}
|
fontSize={1}
|
||||||
mt={2}
|
fontWeight='500'
|
||||||
height={5}
|
px={3}
|
||||||
justifyContent='center'
|
py={2}
|
||||||
alignItems='center'
|
onClick={props.onClick}
|
||||||
width='100%'
|
>
|
||||||
>
|
<Text fontWeight='500' color={props.color}>
|
||||||
<Rule borderColor='lightBlue' />
|
{props.children}
|
||||||
<VisibilitySensor onChange={setVisible}>
|
</Text>
|
||||||
<Text color='blue' fontSize={0} flexShrink='0' px={2}>
|
</Row>
|
||||||
New messages below
|
);
|
||||||
</Text>
|
};
|
||||||
</VisibilitySensor>
|
|
||||||
<Rule borderColor='lightBlue' />
|
const MessageActions = ({ api, history, msg, group }) => {
|
||||||
</Row>
|
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 {
|
interface ChatMessageProps {
|
||||||
msg: Post;
|
msg: Post;
|
||||||
@ -126,8 +244,7 @@ class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
this.divRef = React.createRef();
|
this.divRef = React.createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {}
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
@ -146,7 +263,7 @@ class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
history,
|
history,
|
||||||
api,
|
api,
|
||||||
highlighted,
|
highlighted,
|
||||||
fontSize,
|
fontSize
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
let { renderSigil } = this.props;
|
let { renderSigil } = this.props;
|
||||||
@ -170,7 +287,6 @@ class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
.unix(msg['time-sent'] / 1000)
|
.unix(msg['time-sent'] / 1000)
|
||||||
.format(renderSigil ? 'h:mm A' : 'h:mm');
|
.format(renderSigil ? 'h:mm A' : 'h:mm');
|
||||||
|
|
||||||
|
|
||||||
const messageProps = {
|
const messageProps = {
|
||||||
msg,
|
msg,
|
||||||
timestamp,
|
timestamp,
|
||||||
@ -183,7 +299,7 @@ class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
api,
|
api,
|
||||||
scrollWindow,
|
scrollWindow,
|
||||||
highlighted,
|
highlighted,
|
||||||
fontSize,
|
fontSize
|
||||||
};
|
};
|
||||||
|
|
||||||
const unreadContainerStyle = {
|
const unreadContainerStyle = {
|
||||||
@ -194,7 +310,7 @@ class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
<Box
|
<Box
|
||||||
ref={this.props.innerRef}
|
ref={this.props.innerRef}
|
||||||
pt={renderSigil ? 2 : 0}
|
pt={renderSigil ? 2 : 0}
|
||||||
pb={isLastMessage ? 4 : 2}
|
pb={isLastMessage ? '20px' : 0}
|
||||||
className={containerClass}
|
className={containerClass}
|
||||||
backgroundColor={highlighted ? 'blue' : 'white'}
|
backgroundColor={highlighted ? 'blue' : 'white'}
|
||||||
style={style}
|
style={style}
|
||||||
@ -203,12 +319,14 @@ class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
<DayBreak when={msg['time-sent']} shimTop={renderSigil} />
|
<DayBreak when={msg['time-sent']} shimTop={renderSigil} />
|
||||||
) : null}
|
) : null}
|
||||||
{renderSigil ? (
|
{renderSigil ? (
|
||||||
<>
|
<MessageWrapper {...messageProps}>
|
||||||
<MessageAuthor pb={'2px'} {...messageProps} />
|
<MessageAuthor pb={1} {...messageProps} />
|
||||||
<Message pl={5} pr={4} {...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}>
|
<Box style={unreadContainerStyle}>
|
||||||
{isLastRead ? (
|
{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 = ({
|
export const MessageAuthor = ({
|
||||||
timestamp,
|
timestamp,
|
||||||
@ -239,9 +359,9 @@ export const MessageAuthor = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const osDark = useLocalState((state) => state.dark);
|
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 dark = theme === 'dark' || (theme === 'auto' && osDark);
|
||||||
const contacts = useContactState(state => state.contacts);
|
const contacts = useContactState((state) => state.contacts);
|
||||||
|
|
||||||
const datestamp = moment
|
const datestamp = moment
|
||||||
.unix(msg['time-sent'] / 1000)
|
.unix(msg['time-sent'] / 1000)
|
||||||
@ -291,19 +411,30 @@ export const MessageAuthor = ({
|
|||||||
display='inline-block'
|
display='inline-block'
|
||||||
style={{ objectFit: 'cover' }}
|
style={{ objectFit: 'cover' }}
|
||||||
src={contact.avatar}
|
src={contact.avatar}
|
||||||
height={16}
|
height={24}
|
||||||
width={16}
|
width={24}
|
||||||
borderRadius={1}
|
borderRadius={1}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Sigil
|
<Box
|
||||||
ship={msg.author}
|
width={24}
|
||||||
size={16}
|
height={24}
|
||||||
color={color}
|
display='flex'
|
||||||
classes={sigilClass}
|
justifyContent='center'
|
||||||
icon
|
alignItems='center'
|
||||||
padding={2}
|
backgroundColor={color}
|
||||||
/>
|
borderRadius={1}
|
||||||
|
>
|
||||||
|
<Sigil
|
||||||
|
ship={msg.author}
|
||||||
|
size={12}
|
||||||
|
display='block'
|
||||||
|
color={color}
|
||||||
|
classes={sigilClass}
|
||||||
|
icon
|
||||||
|
padding={0}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Box display='flex' alignItems='center' {...rest}>
|
<Box display='flex' alignItems='center' {...rest}>
|
||||||
@ -311,9 +442,9 @@ export const MessageAuthor = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowOverlay(true);
|
setShowOverlay(true);
|
||||||
}}
|
}}
|
||||||
height={16}
|
height={24}
|
||||||
pr={2}
|
pr={2}
|
||||||
pl={2}
|
pl={'12px'}
|
||||||
cursor='pointer'
|
cursor='pointer'
|
||||||
position='relative'
|
position='relative'
|
||||||
>
|
>
|
||||||
@ -340,10 +471,10 @@ export const MessageAuthor = ({
|
|||||||
pt={1}
|
pt={1}
|
||||||
pb={1}
|
pb={1}
|
||||||
display='flex'
|
display='flex'
|
||||||
alignItems='center'
|
alignItems='baseline'
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
fontSize={0}
|
fontSize={1}
|
||||||
mr={2}
|
mr={2}
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
mono={nameMono}
|
mono={nameMono}
|
||||||
@ -385,13 +516,15 @@ export const Message = ({
|
|||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const { hovering, bind } = useHovering();
|
const { hovering, bind } = useHovering();
|
||||||
const contacts = useContactState(state => state.contacts);
|
const contacts = useContactState((state) => state.contacts);
|
||||||
return (
|
return (
|
||||||
<Box position='relative' {...rest}>
|
<Box position='relative' {...rest}>
|
||||||
{timestampHover ? (
|
{timestampHover ? (
|
||||||
<Text
|
<Text
|
||||||
display={hovering ? 'block' : 'none'}
|
display={hovering ? 'block' : 'none'}
|
||||||
position='absolute'
|
position='absolute'
|
||||||
|
width='36px'
|
||||||
|
textAlign='right'
|
||||||
left='0'
|
left='0'
|
||||||
top='3px'
|
top='3px'
|
||||||
fontSize={0}
|
fontSize={0}
|
||||||
@ -408,6 +541,7 @@ export const Message = ({
|
|||||||
case 'text':
|
case 'text':
|
||||||
return (
|
return (
|
||||||
<TextContent
|
<TextContent
|
||||||
|
key={i}
|
||||||
api={api}
|
api={api}
|
||||||
fontSize={1}
|
fontSize={1}
|
||||||
lineHeight={'20px'}
|
lineHeight={'20px'}
|
||||||
@ -415,10 +549,11 @@ export const Message = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case 'code':
|
case 'code':
|
||||||
return <CodeContent content={content} />;
|
return <CodeContent key={i} content={content} />;
|
||||||
case 'url':
|
case 'url':
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
key={i}
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
fontSize={1}
|
fontSize={1}
|
||||||
lineHeight='20px'
|
lineHeight='20px'
|
||||||
@ -452,9 +587,10 @@ export const Message = ({
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
case 'mention':
|
case 'mention':
|
||||||
const first = (i) => (i === 0);
|
const first = (i) => i === 0;
|
||||||
return (
|
return (
|
||||||
<Mention
|
<Mention
|
||||||
|
key={i}
|
||||||
first={first(i)}
|
first={first(i)}
|
||||||
group={group}
|
group={group}
|
||||||
scrollWindow={scrollWindow}
|
scrollWindow={scrollWindow}
|
||||||
|
@ -91,7 +91,7 @@ const MessageMarkdown = React.memo((props) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return lines.map((line, i) => (
|
return lines.map((line, i) => (
|
||||||
<>
|
<React.Fragment key={i}>
|
||||||
{i !== 0 && <Row height={2} />}
|
{i !== 0 && <Row height={2} />}
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
{...rest}
|
{...rest}
|
||||||
@ -123,7 +123,7 @@ const MessageMarkdown = React.memo((props) => {
|
|||||||
]
|
]
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</>
|
</React.Fragment>
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ import {
|
|||||||
} from '~/logic/lib/tutorialModal';
|
} from '~/logic/lib/tutorialModal';
|
||||||
import useLaunchState from '~/logic/state/launch';
|
import useLaunchState from '~/logic/state/launch';
|
||||||
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
|
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
|
||||||
|
import useMetadataState from '~/logic/state/metadata';
|
||||||
|
|
||||||
|
|
||||||
const ScrollbarLessBox = styled(Box)`
|
const ScrollbarLessBox = styled(Box)`
|
||||||
@ -48,6 +49,7 @@ export default function LaunchApp(props) {
|
|||||||
const baseHash = useLaunchState(state => state.baseHash);
|
const baseHash = useLaunchState(state => state.baseHash);
|
||||||
const [hashText, setHashText] = useState(baseHash);
|
const [hashText, setHashText] = useState(baseHash);
|
||||||
const [exitingTut, setExitingTut] = useState(false);
|
const [exitingTut, setExitingTut] = useState(false);
|
||||||
|
const associations = useMetadataState(s => s.associations);
|
||||||
const hashBox = (
|
const hashBox = (
|
||||||
<Box
|
<Box
|
||||||
position={["relative", "absolute"]}
|
position={["relative", "absolute"]}
|
||||||
@ -78,7 +80,7 @@ export default function LaunchApp(props) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(query.get('tutorial')) {
|
if(query.get('tutorial')) {
|
||||||
if(hasTutorialGroup(props)) {
|
if(hasTutorialGroup({ associations })) {
|
||||||
nextTutStep();
|
nextTutStep();
|
||||||
} else {
|
} else {
|
||||||
showModal();
|
showModal();
|
||||||
@ -92,7 +94,7 @@ export default function LaunchApp(props) {
|
|||||||
let { hideGroups } = useLocalState(tutSelector);
|
let { hideGroups } = useLocalState(tutSelector);
|
||||||
!hideGroups ? { hideGroups } = calmState : null;
|
!hideGroups ? { hideGroups } = calmState : null;
|
||||||
|
|
||||||
const waiter = useWaitForProps(props);
|
const waiter = useWaitForProps({ ...props, associations });
|
||||||
|
|
||||||
const { modal, showModal } = useModal({
|
const { modal, showModal } = useModal({
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
@ -105,7 +107,7 @@ export default function LaunchApp(props) {
|
|||||||
};
|
};
|
||||||
const onContinue = async (e) => {
|
const onContinue = async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if(!hasTutorialGroup(props)) {
|
if(!hasTutorialGroup({ associations })) {
|
||||||
await props.api.groups.join(TUTORIAL_HOST, TUTORIAL_GROUP);
|
await props.api.groups.join(TUTORIAL_HOST, TUTORIAL_GROUP);
|
||||||
await props.api.settings.putEntry('tutorial', 'joined', Date.now());
|
await props.api.settings.putEntry('tutorial', 'joined', Date.now());
|
||||||
await waiter(hasTutorialGroup);
|
await waiter(hasTutorialGroup);
|
||||||
|
@ -148,17 +148,13 @@ export const LinkItem = (props: LinkItemProps): ReactElement => {
|
|||||||
</Anchor>
|
</Anchor>
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Row minWidth='0' flexShrink={0} width="100%" justifyContent="space-between" py={3} bg="white">
|
<Row minWidth='0' flexShrink={0} width="100%" justifyContent="space-between" py={3} bg="white">
|
||||||
|
|
||||||
<Author
|
<Author
|
||||||
showImage
|
showImage
|
||||||
ship={author}
|
ship={author}
|
||||||
date={node.post['time-sent']}
|
date={node.post['time-sent']}
|
||||||
group={group}
|
group={group}
|
||||||
api={api}
|
/>
|
||||||
></Author>
|
|
||||||
|
|
||||||
<Box ml="auto">
|
<Box ml="auto">
|
||||||
<Link
|
<Link
|
||||||
to={node.post.pending ? '#' : `${baseUrl}/${index}`}
|
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 contact: Contact | undefined = contacts?.[`~${props.patp}`];
|
||||||
|
|
||||||
const showNickname = useShowNickname(contact);
|
const showNickname = useShowNickname(contact);
|
||||||
const name = contact?.nickname || `~${props.patp}`;
|
const name = showNickname ? contact.nickname : `~${props.patp}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text mono={!showNickname}>
|
<Text mono={!showNickname}>
|
||||||
|
@ -85,7 +85,7 @@ export function ProfileHeaderImageEdit(props: any): ReactElement {
|
|||||||
|
|
||||||
export function EditProfile(props: any): ReactElement {
|
export function EditProfile(props: any): ReactElement {
|
||||||
const { contact, ship, api } = props;
|
const { contact, ship, api } = props;
|
||||||
const isPublic = useContactState(state => state.isContactPublic);
|
const isPublic = useContactState((state) => state.isContactPublic);
|
||||||
const [hideCover, setHideCover] = useState(false);
|
const [hideCover, setHideCover] = useState(false);
|
||||||
|
|
||||||
const handleHideCover = (value) => {
|
const handleHideCover = (value) => {
|
||||||
@ -150,7 +150,7 @@ export function EditProfile(props: any): ReactElement {
|
|||||||
<Form width='100%' height='100%'>
|
<Form width='100%' height='100%'>
|
||||||
<ProfileHeader>
|
<ProfileHeader>
|
||||||
<ProfileControls>
|
<ProfileControls>
|
||||||
<Row>
|
<Row alignItems='baseline'>
|
||||||
<Button
|
<Button
|
||||||
type='submit'
|
type='submit'
|
||||||
display='inline'
|
display='inline'
|
||||||
@ -178,7 +178,11 @@ export function EditProfile(props: any): ReactElement {
|
|||||||
</Row>
|
</Row>
|
||||||
<ProfileStatus contact={contact} />
|
<ProfileStatus contact={contact} />
|
||||||
</ProfileControls>
|
</ProfileControls>
|
||||||
<ProfileImages hideCover={hideCover} contact={contact} ship={ship}>
|
<ProfileImages
|
||||||
|
hideCover={hideCover}
|
||||||
|
contact={contact}
|
||||||
|
ship={ship}
|
||||||
|
>
|
||||||
<ProfileHeaderImageEdit
|
<ProfileHeaderImageEdit
|
||||||
contact={contact}
|
contact={contact}
|
||||||
setFieldValue={setFieldValue}
|
setFieldValue={setFieldValue}
|
||||||
@ -203,11 +207,7 @@ export function EditProfile(props: any): ReactElement {
|
|||||||
<MarkdownField id='bio' mb={3} />
|
<MarkdownField id='bio' mb={3} />
|
||||||
</Col>
|
</Col>
|
||||||
<Checkbox mb={3} id='isPublic' label='Public Profile' />
|
<Checkbox mb={3} id='isPublic' label='Public Profile' />
|
||||||
<GroupSearch
|
<GroupSearch label='Pinned Groups' id='groups' publicOnly />
|
||||||
label='Pinned Groups'
|
|
||||||
id='groups'
|
|
||||||
publicOnly
|
|
||||||
/>
|
|
||||||
<AsyncButton primary loadingText='Updating...' border mt={3}>
|
<AsyncButton primary loadingText='Updating...' border mt={3}>
|
||||||
Submit
|
Submit
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
|
@ -15,8 +15,8 @@ export function ProfileHeader(props: any): ReactElement {
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
border='1px solid'
|
border='1px solid'
|
||||||
borderColor='lightGray'
|
borderColor='washedGray'
|
||||||
borderRadius='2'
|
borderRadius='3'
|
||||||
overflow='hidden'
|
overflow='hidden'
|
||||||
marginBottom='calc(64px + 2rem)'
|
marginBottom='calc(64px + 2rem)'
|
||||||
>
|
>
|
||||||
@ -65,7 +65,7 @@ export function ProfileImages(props: any): ReactElement {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Row ref={anchorRef} width='100%' height='300px' position='relative'>
|
<Row ref={anchorRef} width='100%' height='400px' position='relative'>
|
||||||
{cover}
|
{cover}
|
||||||
<Center position='absolute' width='100%' height='100%'>
|
<Center position='absolute' width='100%' height='100%'>
|
||||||
{props.children}
|
{props.children}
|
||||||
@ -74,7 +74,7 @@ export function ProfileImages(props: any): ReactElement {
|
|||||||
<Box
|
<Box
|
||||||
height='128px'
|
height='128px'
|
||||||
width='128px'
|
width='128px'
|
||||||
borderRadius='2'
|
borderRadius='3'
|
||||||
overflow='hidden'
|
overflow='hidden'
|
||||||
position='absolute'
|
position='absolute'
|
||||||
left='50%'
|
left='50%'
|
||||||
|
@ -39,7 +39,7 @@ export function ViewProfile(props: any): ReactElement {
|
|||||||
</ProfileHeader>
|
</ProfileHeader>
|
||||||
<Row pb={2} alignItems='center' width='100%'>
|
<Row pb={2} alignItems='center' width='100%'>
|
||||||
<Center width='100%'>
|
<Center width='100%'>
|
||||||
<Text>
|
<Text fontWeight='500'>
|
||||||
{!hideNicknames && contact?.nickname ? contact.nickname : ''}
|
{!hideNicknames && contact?.nickname ? contact.nickname : ''}
|
||||||
</Text>
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
@ -51,7 +51,7 @@ export function ViewProfile(props: any): ReactElement {
|
|||||||
</Text>
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
</Row>
|
</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'>
|
<Center flexDirection='column' maxWidth='32rem'>
|
||||||
<RichText width='100%' disableRemoteContent>
|
<RichText width='100%' disableRemoteContent>
|
||||||
{contact?.bio ? contact.bio : ''}
|
{contact?.bio ? contact.bio : ''}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
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 ReactMarkdown from 'react-markdown';
|
||||||
import bigInt from 'big-integer';
|
import bigInt from 'big-integer';
|
||||||
|
|
||||||
@ -9,6 +9,7 @@ import { Comments } from '~/views/components/Comments';
|
|||||||
import { NoteNavigation } from './NoteNavigation';
|
import { NoteNavigation } from './NoteNavigation';
|
||||||
import GlobalApi from '~/logic/api/global';
|
import GlobalApi from '~/logic/api/global';
|
||||||
import { getLatestRevision, getComments } from '~/logic/lib/publish';
|
import { getLatestRevision, getComments } from '~/logic/lib/publish';
|
||||||
|
import { roleForShip } from '~/logic/lib/group';
|
||||||
import Author from '~/views/components/Author';
|
import Author from '~/views/components/Author';
|
||||||
import { Contacts, GraphNode, Graph, Association, Unreads, Group } from '@urbit/api';
|
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');
|
api.hark.markEachAsRead(props.association, '/',`/${index[1]}/1/1`, 'note', 'publish');
|
||||||
}, [props.association, props.note]);
|
}, [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) {
|
if (window.ship === note?.post?.author) {
|
||||||
adminLinks = (
|
adminLinks.push(
|
||||||
<Box display="inline-block" verticalAlign="middle">
|
<Link
|
||||||
<Link to={`${baseUrl}/edit`}>
|
style={{ 'display': 'inline-block' }}
|
||||||
<Text
|
to={`${baseUrl}/edit`}
|
||||||
color="green"
|
|
||||||
ml={2}
|
|
||||||
>
|
>
|
||||||
Update
|
<Text
|
||||||
</Text>
|
color="blue"
|
||||||
|
ml={2}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</Text>
|
||||||
</Link>
|
</Link>
|
||||||
<Text
|
)
|
||||||
color="red"
|
};
|
||||||
ml={2}
|
|
||||||
onClick={deletePost}
|
if (window.ship === note?.post?.author || ourRole === "admin") {
|
||||||
style={{ cursor: 'pointer' }}
|
adminLinks.push(
|
||||||
>
|
<Text
|
||||||
Delete
|
color="red"
|
||||||
</Text>
|
display='inline-block'
|
||||||
</Box>
|
ml={2}
|
||||||
);
|
onClick={deletePost}
|
||||||
}
|
style={{ cursor: 'pointer' }}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
const windowRef = React.useRef(null);
|
const windowRef = React.useRef(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -103,13 +112,15 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
|||||||
</Link>
|
</Link>
|
||||||
<Col>
|
<Col>
|
||||||
<Text display="block" mb={2}>{title || ''}</Text>
|
<Text display="block" mb={2}>{title || ''}</Text>
|
||||||
<Box display="flex">
|
<Row alignItems="center">
|
||||||
<Author
|
<Author
|
||||||
|
showImage
|
||||||
ship={post?.author}
|
ship={post?.author}
|
||||||
date={post?.['time-sent']}
|
date={post?.['time-sent']}
|
||||||
|
group={group}
|
||||||
/>
|
/>
|
||||||
<Text ml={2}>{adminLinks}</Text>
|
<Text ml={1}>{adminLinks}</Text>
|
||||||
</Box>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
<Box color="black" className="md" style={{ overflowWrap: 'break-word', overflow: 'hidden' }}>
|
<Box color="black" className="md" style={{ overflowWrap: 'break-word', overflow: 'hidden' }}>
|
||||||
<ReactMarkdown source={body} linkTarget={'_blank'} renderers={renderers} />
|
<ReactMarkdown source={body} linkTarget={'_blank'} renderers={renderers} />
|
||||||
|
@ -12,7 +12,6 @@ import {
|
|||||||
getSnippet
|
getSnippet
|
||||||
} from '~/logic/lib/publish';
|
} from '~/logic/lib/publish';
|
||||||
import { Unreads } from '@urbit/api';
|
import { Unreads } from '@urbit/api';
|
||||||
import GlobalApi from '~/logic/api/global';
|
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import useHarkState from '~/logic/state/hark';
|
import useHarkState from '~/logic/state/hark';
|
||||||
|
|
||||||
@ -21,7 +20,6 @@ interface NotePreviewProps {
|
|||||||
book: string;
|
book: string;
|
||||||
node: GraphNode;
|
node: GraphNode;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
api: GlobalApi;
|
|
||||||
group: Group;
|
group: Group;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +94,6 @@ export function NotePreview(props: NotePreviewProps) {
|
|||||||
date={post?.['time-sent']}
|
date={post?.['time-sent']}
|
||||||
group={group}
|
group={group}
|
||||||
unread={isUnread}
|
unread={isUnread}
|
||||||
api={props.api}
|
|
||||||
/>
|
/>
|
||||||
<Box ml="auto" mr={1}>
|
<Box ml="auto" mr={1}>
|
||||||
<Link to={url}>
|
<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 { Contacts, Rolodex, Groups, Associations, Graph, Association, Unreads } from '@urbit/api';
|
||||||
|
|
||||||
import { NotebookPosts } from './NotebookPosts';
|
import { NotebookPosts } from './NotebookPosts';
|
||||||
import GlobalApi from '~/logic/api/global';
|
|
||||||
import { useShowNickname } from '~/logic/lib/util';
|
import { useShowNickname } from '~/logic/lib/util';
|
||||||
import useContactState from '~/logic/state/contact';
|
import useContactState from '~/logic/state/contact';
|
||||||
import useGroupState from '~/logic/state/group';
|
import useGroupState from '~/logic/state/group';
|
||||||
|
|
||||||
interface NotebookProps {
|
interface NotebookProps {
|
||||||
api: GlobalApi;
|
|
||||||
ship: string;
|
ship: string;
|
||||||
book: string;
|
book: string;
|
||||||
graph: Graph;
|
graph: Graph;
|
||||||
@ -40,7 +38,6 @@ export function Notebook(props: NotebookProps & RouteComponentProps): ReactEleme
|
|||||||
const contacts = useContactState(state => state.contacts);
|
const contacts = useContactState(state => state.contacts);
|
||||||
|
|
||||||
const contact = contacts?.[`~${ship}`];
|
const contact = contacts?.[`~${ship}`];
|
||||||
console.log(association.resource);
|
|
||||||
|
|
||||||
const showNickname = useShowNickname(contact);
|
const showNickname = useShowNickname(contact);
|
||||||
|
|
||||||
@ -61,7 +58,6 @@ export function Notebook(props: NotebookProps & RouteComponentProps): ReactEleme
|
|||||||
host={ship}
|
host={ship}
|
||||||
book={book}
|
book={book}
|
||||||
baseUrl={props.baseUrl}
|
baseUrl={props.baseUrl}
|
||||||
api={props.api}
|
|
||||||
group={group}
|
group={group}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -11,7 +11,6 @@ interface NotebookPostsProps {
|
|||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
hideAvatars?: boolean;
|
hideAvatars?: boolean;
|
||||||
hideNicknames?: boolean;
|
hideNicknames?: boolean;
|
||||||
api: GlobalApi;
|
|
||||||
group: Group;
|
group: Group;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +28,6 @@ export function NotebookPosts(props: NotebookPostsProps) {
|
|||||||
contact={contacts[`~${node.post.author}`]}
|
contact={contacts[`~${node.post.author}`]}
|
||||||
node={node}
|
node={node}
|
||||||
baseUrl={props.baseUrl}
|
baseUrl={props.baseUrl}
|
||||||
api={props.api}
|
|
||||||
group={props.group}
|
group={props.group}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -4,8 +4,16 @@ import { Text } from '@tlon/indigo-react';
|
|||||||
|
|
||||||
export function BackButton(props: {}) {
|
export function BackButton(props: {}) {
|
||||||
return (
|
return (
|
||||||
<Link to="/~settings">
|
<Link to='/~settings'>
|
||||||
<Text display={["block", "none"]} fontSize="2" fontWeight="medium">{"<- Back to System Preferences"}</Text>
|
<Text
|
||||||
|
display={['block', 'none']}
|
||||||
|
fontSize='2'
|
||||||
|
fontWeight='medium'
|
||||||
|
p={4}
|
||||||
|
pb={0}
|
||||||
|
>
|
||||||
|
{'<- Back to System Preferences'}
|
||||||
|
</Text>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -11,12 +11,12 @@ import {
|
|||||||
Anchor
|
Anchor
|
||||||
} from '@tlon/indigo-react';
|
} from '@tlon/indigo-react';
|
||||||
|
|
||||||
import GlobalApi from "~/logic/api/global";
|
import GlobalApi from '~/logic/api/global';
|
||||||
import { BucketList } from "./BucketList";
|
import { BucketList } from './BucketList';
|
||||||
import { S3State } from '~/types/s3-update';
|
import { S3State } from '~/types/s3-update';
|
||||||
import useS3State from '~/logic/state/storage';
|
import useS3State from '~/logic/state/storage';
|
||||||
import { BackButton } from './BackButton';
|
import { BackButton } from './BackButton';
|
||||||
import {StorageState} from '~/types';
|
import { StorageState } from '~/types';
|
||||||
import useStorageState from '~/logic/state/storage';
|
import useStorageState from '~/logic/state/storage';
|
||||||
|
|
||||||
interface FormSchema {
|
interface FormSchema {
|
||||||
@ -33,7 +33,7 @@ interface S3FormProps {
|
|||||||
|
|
||||||
export default function S3Form(props: S3FormProps): ReactElement {
|
export default function S3Form(props: S3FormProps): ReactElement {
|
||||||
const { api } = props;
|
const { api } = props;
|
||||||
const s3 = useStorageState(state => state.s3);
|
const s3 = useStorageState((state) => state.s3);
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
const onSubmit = useCallback(
|
||||||
(values: FormSchema) => {
|
(values: FormSchema) => {
|
||||||
@ -54,7 +54,8 @@ export default function S3Form(props: S3FormProps): ReactElement {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Col p="5" pt="4" borderBottom="1" borderBottomColor="washedGray">
|
<BackButton />
|
||||||
|
<Col p='5' pt='4' borderBottom='1' borderBottomColor='washedGray'>
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={
|
initialValues={
|
||||||
{
|
{
|
||||||
@ -68,42 +69,42 @@ export default function S3Form(props: S3FormProps): ReactElement {
|
|||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<BackButton/>
|
<Col maxWidth='600px' gapY='5'>
|
||||||
<Col maxWidth="600px" gapY="5">
|
<Col gapY='1' mt='0'>
|
||||||
<Col gapY="1" mt="0">
|
<Text color='black' fontSize={2} fontWeight='medium'>
|
||||||
<Text color="black" fontSize={2} fontWeight="medium">
|
|
||||||
S3 Storage Setup
|
S3 Storage Setup
|
||||||
</Text>
|
</Text>
|
||||||
<Text gray>
|
<Text gray>
|
||||||
Store credentials for your S3 object storage buckets on your
|
Store credentials for your S3 object storage buckets on your
|
||||||
Urbit ship, and upload media freely to various modules.
|
Urbit ship, and upload media freely to various modules.
|
||||||
<Anchor
|
<Anchor
|
||||||
target="_blank"
|
target='_blank'
|
||||||
style={{ textDecoration: 'none' }}
|
style={{ textDecoration: 'none' }}
|
||||||
borderBottom="1"
|
borderBottom='1'
|
||||||
ml="1"
|
ml='1'
|
||||||
href="https://urbit.org/using/operations/using-your-ship/#bucket-setup">
|
href='https://urbit.org/using/operations/using-your-ship/#bucket-setup'
|
||||||
|
>
|
||||||
Learn more
|
Learn more
|
||||||
</Anchor>
|
</Anchor>
|
||||||
</Text>
|
</Text>
|
||||||
</Col>
|
</Col>
|
||||||
<Input label="Endpoint" id="s3endpoint" />
|
<Input label='Endpoint' id='s3endpoint' />
|
||||||
<Input label="Access Key ID" id="s3accessKeyId" />
|
<Input label='Access Key ID' id='s3accessKeyId' />
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type='password'
|
||||||
label="Secret Access Key"
|
label='Secret Access Key'
|
||||||
id="s3secretAccessKey"
|
id='s3secretAccessKey'
|
||||||
/>
|
/>
|
||||||
<Button style={{ cursor: "pointer" }} type="submit">
|
<Button style={{ cursor: 'pointer' }} type='submit'>
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
</Col>
|
</Col>
|
||||||
<Col maxWidth="600px" p="5" gapY="4">
|
<Col maxWidth='600px' p='5' gapY='4'>
|
||||||
<Col gapY="1">
|
<Col gapY='1'>
|
||||||
<Text color="black" mb={4} fontSize={2} fontWeight="medium">
|
<Text color='black' mb={4} fontSize={2} fontWeight='medium'>
|
||||||
S3 Buckets
|
S3 Buckets
|
||||||
</Text>
|
</Text>
|
||||||
<Text gray>
|
<Text gray>
|
||||||
|
@ -1,35 +1,42 @@
|
|||||||
import React, { ReactNode } from "react";
|
import React, { ReactNode } from 'react';
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from 'react-router-dom';
|
||||||
import Helmet from "react-helmet";
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
import { Text, Box, Col, Row } from '@tlon/indigo-react';
|
import { Text, Box, Col, Row } from '@tlon/indigo-react';
|
||||||
|
|
||||||
import { NotificationPreferences } from "./components/lib/NotificationPref";
|
import { NotificationPreferences } from './components/lib/NotificationPref';
|
||||||
import DisplayForm from "./components/lib/DisplayForm";
|
import DisplayForm from './components/lib/DisplayForm';
|
||||||
import S3Form from "./components/lib/S3Form";
|
import S3Form from './components/lib/S3Form';
|
||||||
import { CalmPrefs } from "./components/lib/CalmPref";
|
import { CalmPrefs } from './components/lib/CalmPref';
|
||||||
import SecuritySettings from "./components/lib/Security";
|
import SecuritySettings from './components/lib/Security';
|
||||||
import { LeapSettings } from "./components/lib/LeapSettings";
|
import { LeapSettings } from './components/lib/LeapSettings';
|
||||||
import { useHashLink } from "~/logic/lib/useHashLink";
|
import { useHashLink } from '~/logic/lib/useHashLink';
|
||||||
import { SidebarItem as BaseSidebarItem } from "~/views/landscape/components/SidebarItem";
|
import { SidebarItem as BaseSidebarItem } from '~/views/landscape/components/SidebarItem';
|
||||||
import { PropFunc } from "~/types";
|
import { PropFunc } from '~/types';
|
||||||
|
|
||||||
export const Skeleton = (props: { children: ReactNode }) => (
|
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
|
<Box
|
||||||
height="100%"
|
display='grid'
|
||||||
width="100%"
|
gridTemplateColumns={[
|
||||||
borderRadius={1}
|
'100%',
|
||||||
bg="white"
|
'minmax(150px, 1fr) 3fr',
|
||||||
|
'minmax(250px, 1fr) 4fr'
|
||||||
|
]}
|
||||||
|
gridTemplateRows='100%'
|
||||||
|
height='100%'
|
||||||
|
width='100%'
|
||||||
|
borderRadius={2}
|
||||||
|
bg='white'
|
||||||
border={1}
|
border={1}
|
||||||
borderColor="washedGray"
|
borderColor='washedGray'
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
type ProvSideProps = "to" | "selected";
|
type ProvSideProps = 'to' | 'selected';
|
||||||
type BaseProps = PropFunc<typeof BaseSidebarItem>;
|
type BaseProps = PropFunc<typeof BaseSidebarItem>;
|
||||||
function SidebarItem(props: { hash: string } & Omit<BaseProps, ProvSideProps>) {
|
function SidebarItem(props: { hash: string } & Omit<BaseProps, ProvSideProps>) {
|
||||||
const { hash, icon, text, ...rest } = props;
|
const { hash, icon, text, ...rest } = props;
|
||||||
@ -54,16 +61,15 @@ function SettingsItem(props: { children: ReactNode }) {
|
|||||||
const { children } = props;
|
const { children } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box borderBottom="1" borderBottomColor="washedGray">
|
<Box borderBottom='1' borderBottomColor='washedGray'>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SettingsScreen(props: any) {
|
export default function SettingsScreen(props: any) {
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const hash = location.hash.slice(1)
|
const hash = location.hash.slice(1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -71,68 +77,49 @@ export default function SettingsScreen(props: any) {
|
|||||||
<title>Landscape - Settings</title>
|
<title>Landscape - Settings</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<Skeleton>
|
<Skeleton>
|
||||||
<Row height="100%" overflow="hidden">
|
<Col
|
||||||
<Col
|
height='100%'
|
||||||
height="100%"
|
borderRight='1'
|
||||||
borderRight="1"
|
borderRightColor='washedGray'
|
||||||
borderRightColor="washedGray"
|
display={hash === '' ? 'flex' : ['none', 'flex']}
|
||||||
display={hash === "" ? "flex" : ["none", "flex"]}
|
width='100%'
|
||||||
minWidth="250px"
|
overflowY='auto'
|
||||||
width="100%"
|
>
|
||||||
maxWidth={["100vw", "350px"]}
|
<Text display='block' mt='4' mb='3' mx='3' fontSize='2' fontWeight='700'>
|
||||||
>
|
System Preferences
|
||||||
<Text
|
</Text>
|
||||||
display="block"
|
<Col>
|
||||||
my="4"
|
<SidebarItem
|
||||||
mx="3"
|
icon='Inbox'
|
||||||
fontSize="2"
|
text='Notifications'
|
||||||
fontWeight="medium"
|
hash='notifications'
|
||||||
>
|
/>
|
||||||
System Preferences
|
<SidebarItem icon='Image' text='Display' hash='display' />
|
||||||
</Text>
|
<SidebarItem icon='Upload' text='Remote Storage' hash='s3' />
|
||||||
<Col gapY="1">
|
<SidebarItem icon='LeapArrow' text='Leap' hash='leap' />
|
||||||
<SidebarItem
|
<SidebarItem icon='Node' text='CalmEngine' hash='calm' />
|
||||||
icon="Inbox"
|
<SidebarItem
|
||||||
text="Notifications"
|
icon='Locked'
|
||||||
hash="notifications"
|
text='Devices + Security'
|
||||||
/>
|
hash='security'
|
||||||
<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"
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Col>
|
</Col>
|
||||||
<Col flexGrow={1} overflowY="auto">
|
</Col>
|
||||||
<SettingsItem>
|
<Col flexGrow={1} overflowY='auto'>
|
||||||
{hash === "notifications" && (
|
<SettingsItem>
|
||||||
<NotificationPreferences
|
{hash === 'notifications' && (
|
||||||
{...props}
|
<NotificationPreferences
|
||||||
graphConfig={props.notificationsGraphConfig}
|
{...props}
|
||||||
/>
|
graphConfig={props.notificationsGraphConfig}
|
||||||
)}
|
/>
|
||||||
{hash === "display" && (
|
)}
|
||||||
<DisplayForm api={props.api} />
|
{hash === 'display' && <DisplayForm api={props.api} />}
|
||||||
)}
|
{hash === 's3' && <S3Form api={props.api} />}
|
||||||
{hash === "s3" && (
|
{hash === 'leap' && <LeapSettings api={props.api} />}
|
||||||
<S3Form api={props.api} />
|
{hash === 'calm' && <CalmPrefs api={props.api} />}
|
||||||
)}
|
{hash === 'security' && <SecuritySettings api={props.api} />}
|
||||||
{hash === "leap" && (
|
</SettingsItem>
|
||||||
<LeapSettings api={props.api} />
|
</Col>
|
||||||
)}
|
|
||||||
{hash === "calm" && (
|
|
||||||
<CalmPrefs api={props.api} />
|
|
||||||
)}
|
|
||||||
{hash === "security" && (
|
|
||||||
<SecuritySettings api={props.api} />
|
|
||||||
)}
|
|
||||||
</SettingsItem>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -11,7 +11,6 @@ import useSettingsState, {selectCalmState} from "~/logic/state/settings";
|
|||||||
import useLocalState from "~/logic/state/local";
|
import useLocalState from "~/logic/state/local";
|
||||||
import OverlaySigil from './OverlaySigil';
|
import OverlaySigil from './OverlaySigil';
|
||||||
import { Sigil } from '~/logic/lib/sigil';
|
import { Sigil } from '~/logic/lib/sigil';
|
||||||
import GlobalApi from '~/logic/api/global';
|
|
||||||
import Timestamp from './Timestamp';
|
import Timestamp from './Timestamp';
|
||||||
import useContactState from '~/logic/state/contact';
|
import useContactState from '~/logic/state/contact';
|
||||||
|
|
||||||
@ -22,7 +21,6 @@ interface AuthorProps {
|
|||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
unread?: boolean;
|
unread?: boolean;
|
||||||
group: Group;
|
group: Group;
|
||||||
api?: GlobalApi;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line max-lines-per-function
|
// eslint-disable-next-line max-lines-per-function
|
||||||
|
@ -10,6 +10,7 @@ import { Group } from '@urbit/api';
|
|||||||
import GlobalApi from '~/logic/api/global';
|
import GlobalApi from '~/logic/api/global';
|
||||||
import Author from '~/views/components/Author';
|
import Author from '~/views/components/Author';
|
||||||
import { MentionText } from '~/views/components/MentionText';
|
import { MentionText } from '~/views/components/MentionText';
|
||||||
|
import { roleForShip } from '~/logic/lib/group';
|
||||||
import { getLatestCommentRevision } from '~/logic/lib/publish';
|
import { getLatestCommentRevision } from '~/logic/lib/publish';
|
||||||
|
|
||||||
const ClickBox = styled(Box)`
|
const ClickBox = styled(Box)`
|
||||||
@ -31,7 +32,7 @@ interface CommentItemProps {
|
|||||||
export function CommentItem(props: CommentItemProps): ReactElement {
|
export function CommentItem(props: CommentItemProps): ReactElement {
|
||||||
const { ship, name, api, comment, group } = props;
|
const { ship, name, api, comment, group } = props;
|
||||||
const [, post] = getLatestCommentRevision(comment);
|
const [, post] = getLatestCommentRevision(comment);
|
||||||
const disabled = props.pending || window.ship !== post?.author;
|
const disabled = props.pending;
|
||||||
|
|
||||||
const onDelete = async () => {
|
const onDelete = async () => {
|
||||||
await api.graph.removeNodes(ship, name, [comment.post?.index]);
|
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 commentIndex = commentIndexArray[commentIndexArray.length - 1];
|
||||||
const updateUrl = `${props.baseUrl}/${commentIndex}`;
|
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 (
|
return (
|
||||||
<Box mb={4} opacity={post?.pending ? '60%' : '100%'}>
|
<Box mb={4} opacity={post?.pending ? '60%' : '100%'}>
|
||||||
<Row bg="white" my={3}>
|
<Row bg="white" my={3}>
|
||||||
@ -50,23 +74,10 @@ export function CommentItem(props: CommentItemProps): ReactElement {
|
|||||||
date={post?.['time-sent']}
|
date={post?.['time-sent']}
|
||||||
unread={props.unread}
|
unread={props.unread}
|
||||||
group={group}
|
group={group}
|
||||||
api={api}
|
|
||||||
>
|
>
|
||||||
{!disabled && (
|
<Row alignItems="center">
|
||||||
<Box display="inline-block" verticalAlign="middle">
|
{adminLinks}
|
||||||
<Link to={updateUrl}>
|
</Row>
|
||||||
<Text
|
|
||||||
color="green"
|
|
||||||
ml={2}
|
|
||||||
>
|
|
||||||
Update
|
|
||||||
</Text>
|
|
||||||
</Link>
|
|
||||||
<ClickBox display="inline-block" color="red" onClick={onDelete}>
|
|
||||||
<Text color='red'>Delete</Text>
|
|
||||||
</ClickBox>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Author>
|
</Author>
|
||||||
</Row>
|
</Row>
|
||||||
<Box mb={2}>
|
<Box mb={2}>
|
||||||
|
@ -51,7 +51,6 @@ class RemoteContent extends Component<RemoteContentProps, RemoteContentState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
save = () => {
|
save = () => {
|
||||||
console.log(`saving for: ${this.props.url}`);
|
|
||||||
if(this.saving) {
|
if(this.saving) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -60,7 +59,6 @@ class RemoteContent extends Component<RemoteContentProps, RemoteContentState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
restore = () => {
|
restore = () => {
|
||||||
console.log(`restoring for: ${this.props.url}`);
|
|
||||||
this.saving = false;
|
this.saving = false;
|
||||||
this.props.restore();
|
this.props.restore();
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
BaseImage
|
BaseImage
|
||||||
} from '@tlon/indigo-react';
|
} from '@tlon/indigo-react';
|
||||||
|
|
||||||
import ReconnectButton from './ReconnectButton';
|
import ReconnectButton from './ReconnectButton';
|
||||||
import { Dropdown } from './Dropdown';
|
import { Dropdown } from './Dropdown';
|
||||||
import { StatusBarItem } from './StatusBarItem';
|
import { StatusBarItem } from './StatusBarItem';
|
||||||
@ -19,6 +20,7 @@ import { useTutorialModal } from './useTutorialModal';
|
|||||||
|
|
||||||
import useHarkState from '~/logic/state/hark';
|
import useHarkState from '~/logic/state/hark';
|
||||||
import useInviteState from '~/logic/state/invite';
|
import useInviteState from '~/logic/state/invite';
|
||||||
|
import useContactState from '~/logic/state/contact';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import useLocalState, { selectLocalState } from '~/logic/state/local';
|
import useLocalState, { selectLocalState } from '~/logic/state/local';
|
||||||
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
|
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
|
||||||
@ -26,8 +28,9 @@ import useSettingsState, { selectCalmState } from '~/logic/state/settings';
|
|||||||
const localSel = selectLocalState(['toggleOmnibox']);
|
const localSel = selectLocalState(['toggleOmnibox']);
|
||||||
|
|
||||||
const StatusBar = (props) => {
|
const StatusBar = (props) => {
|
||||||
const { ourContact, api, ship } = props;
|
const { api, ship } = props;
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
const ourContact = useContactState((state) => state.contacts[`~${ship}`]);
|
||||||
const notificationsCount = useHarkState((state) => state.notificationsCount);
|
const notificationsCount = useHarkState((state) => state.notificationsCount);
|
||||||
const doNotDisturb = useHarkState((state) => state.doNotDisturb);
|
const doNotDisturb = useHarkState((state) => state.doNotDisturb);
|
||||||
const inviteState = useInviteState((state) => state.invites);
|
const inviteState = useInviteState((state) => state.invites);
|
||||||
@ -38,7 +41,7 @@ const StatusBar = (props) => {
|
|||||||
const { toggleOmnibox } = useLocalState(localSel);
|
const { toggleOmnibox } = useLocalState(localSel);
|
||||||
const { hideAvatars } = useSettingsState(selectCalmState);
|
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 xPadding = !hideAvatars && ourContact?.avatar ? '0' : '2';
|
||||||
const bgColor = !hideAvatars && ourContact?.avatar ? '' : color;
|
const bgColor = !hideAvatars && ourContact?.avatar ? '' : color;
|
||||||
const profileImage =
|
const profileImage =
|
||||||
|
@ -9,15 +9,13 @@ import {
|
|||||||
StatelessAsyncButton as AsyncButton,
|
StatelessAsyncButton as AsyncButton,
|
||||||
StatelessAsyncButton
|
StatelessAsyncButton
|
||||||
} from './StatelessAsyncButton';
|
} from './StatelessAsyncButton';
|
||||||
import { Notebooks, Graphs, Inbox } from '@urbit/api';
|
import { Graphs } from '@urbit/api';
|
||||||
import useGraphState from '~/logic/state/graph';
|
import useGraphState from '~/logic/state/graph';
|
||||||
|
|
||||||
interface UnjoinedResourceProps {
|
interface UnjoinedResourceProps {
|
||||||
association: Association;
|
association: Association;
|
||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
notebooks: Notebooks;
|
|
||||||
inbox: Inbox;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isJoined(path: string) {
|
function isJoined(path: string) {
|
||||||
@ -31,7 +29,7 @@ function isJoined(path: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function UnjoinedResource(props: UnjoinedResourceProps) {
|
export function UnjoinedResource(props: UnjoinedResourceProps) {
|
||||||
const { api, notebooks, inbox } = props;
|
const { api } = props;
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const rid = props.association.resource;
|
const rid = props.association.resource;
|
||||||
const appName = props.association['app-name'];
|
const appName = props.association['app-name'];
|
||||||
@ -52,7 +50,7 @@ export function UnjoinedResource(props: UnjoinedResourceProps) {
|
|||||||
if (isJoined(rid)({ graphKeys })) {
|
if (isJoined(rid)({ graphKeys })) {
|
||||||
history.push(`${props.baseUrl}/resource/${app}${rid}`);
|
history.push(`${props.baseUrl}/resource/${app}${rid}`);
|
||||||
}
|
}
|
||||||
}, [props.association, inbox, graphKeys, notebooks]);
|
}, [props.association, graphKeys]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Center p={6}>
|
<Center p={6}>
|
||||||
|
@ -40,7 +40,7 @@ interface VirtualScrollerProps<T> {
|
|||||||
data: BigIntOrderedMap<T>;
|
data: BigIntOrderedMap<T>;
|
||||||
/**
|
/**
|
||||||
* The component to render the items
|
* The component to render the items
|
||||||
*
|
*
|
||||||
* @remarks
|
* @remarks
|
||||||
*
|
*
|
||||||
* This component must be referentially stable, so either use `useCallback` or
|
* This component must be referentially stable, so either use `useCallback` or
|
||||||
@ -157,7 +157,7 @@ export default class VirtualScroller<T> extends Component<VirtualScrollerProps<T
|
|||||||
this.loaded.top = true;
|
this.loaded.top = true;
|
||||||
this.loaded.bottom = true;
|
this.loaded.bottom = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateVisible(0);
|
this.updateVisible(0);
|
||||||
this.resetScroll();
|
this.resetScroll();
|
||||||
this.loadRows(false);
|
this.loadRows(false);
|
||||||
@ -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" />)}
|
{!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)' }}>
|
<Box style={{ transform, width: 'calc(100% - 4px)' }}>
|
||||||
{(isTop ? !atStart : !atEnd) && (<Center height="5">
|
{(isTop ? !atStart : !atEnd) && (<Center height="5">
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
|
@ -16,9 +16,7 @@ export function useTutorialModal(
|
|||||||
setTutorialRef(anchorRef.current);
|
setTutorialRef(anchorRef.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {}
|
||||||
console.log(tutorialProgress);
|
|
||||||
}
|
|
||||||
}, [tutorialProgress, show, anchorRef]);
|
}, [tutorialProgress, show, anchorRef]);
|
||||||
|
|
||||||
return show && onProgress === tutorialProgress;
|
return show && onProgress === tutorialProgress;
|
||||||
|
@ -10,16 +10,14 @@
|
|||||||
font-family: 'Inter';
|
font-family: 'Inter';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
src: url("/~landscape/fonts/inter-medium.woff2") format("woff2"),
|
src: url("https://media.urbit.org/fonts/Inter-Medium.woff2") format("woff2");
|
||||||
url("https://media.urbit.org/fonts/Inter-Medium.woff2") format("woff2");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Inter';
|
font-family: 'Inter';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
src: url("/~landscape/fonts/inter-semibold.woff2") format("woff2"),
|
src: url("https://media.urbit.org/fonts/Inter-SemiBold.woff2") format("woff2");
|
||||||
url("https://media.urbit.org/fonts/Inter-SemiBold.woff2") format("woff2");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
|
@ -158,8 +158,6 @@ export function GroupsPane(props: GroupsPaneProps) {
|
|||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
>
|
>
|
||||||
<UnjoinedResource
|
<UnjoinedResource
|
||||||
notebooks={props.notebooks}
|
|
||||||
inbox={props.inbox}
|
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
api={api}
|
api={api}
|
||||||
association={association}
|
association={association}
|
||||||
@ -191,9 +189,8 @@ export function GroupsPane(props: GroupsPaneProps) {
|
|||||||
<Route
|
<Route
|
||||||
path={relativePath('')}
|
path={relativePath('')}
|
||||||
render={(routeProps) => {
|
render={(routeProps) => {
|
||||||
const hasDescription = groupAssociation?.metadata?.description;
|
const channelCount = Object.keys(associations?.graph ?? {}).filter((e) => {
|
||||||
const channelCount = Object.keys(props?.associations?.graph ?? {}).filter((e) => {
|
return associations?.graph?.[e]?.['group'] === groupPath;
|
||||||
return props?.associations?.graph?.[e]?.['group'] === groupPath;
|
|
||||||
}).length;
|
}).length;
|
||||||
let summary: ReactNode;
|
let summary: ReactNode;
|
||||||
if(groupAssociation?.group) {
|
if(groupAssociation?.group) {
|
||||||
|
@ -15,7 +15,7 @@ export function MetadataIcon(props: MetadataIconProps) {
|
|||||||
const bgColor = metadata.picture ? {} : { bg: `#${uxToHex(metadata.color)}` };
|
const bgColor = metadata.picture ? {} : { bg: `#${uxToHex(metadata.color)}` };
|
||||||
|
|
||||||
return (
|
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} />}
|
{metadata.picture && <Image height="100%" src={metadata.picture} />}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -37,7 +37,6 @@ interface SidebarProps {
|
|||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
selected?: string;
|
selected?: string;
|
||||||
selectedGroup?: string;
|
selectedGroup?: string;
|
||||||
includeUnmanaged?: boolean;
|
|
||||||
apps: SidebarAppConfigs;
|
apps: SidebarAppConfigs;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
mobileHide?: boolean;
|
mobileHide?: boolean;
|
||||||
|
@ -30,7 +30,7 @@ export const SidebarItem = ({
|
|||||||
bgActive="washedGray"
|
bgActive="washedGray"
|
||||||
display="flex"
|
display="flex"
|
||||||
px="3"
|
px="3"
|
||||||
py="1"
|
py="2"
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
|
@ -5,7 +5,6 @@ import { Associations } from '@urbit/api/metadata';
|
|||||||
|
|
||||||
import { Sidebar } from './Sidebar/Sidebar';
|
import { Sidebar } from './Sidebar/Sidebar';
|
||||||
import GlobalApi from '~/logic/api/global';
|
import GlobalApi from '~/logic/api/global';
|
||||||
import GlobalSubscription from '~/logic/subscription/global';
|
|
||||||
import { useGraphModule } from './Sidebar/Apps';
|
import { useGraphModule } from './Sidebar/Apps';
|
||||||
import { Body } from '~/views/components/Body';
|
import { Body } from '~/views/components/Body';
|
||||||
import { Workspace } from '~/types/workspace';
|
import { Workspace } from '~/types/workspace';
|
||||||
@ -16,14 +15,11 @@ import ErrorBoundary from '~/views/components/ErrorBoundary';
|
|||||||
interface SkeletonProps {
|
interface SkeletonProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
recentGroups: string[];
|
recentGroups: string[];
|
||||||
linkListening: Set<Path>;
|
|
||||||
selected?: string;
|
selected?: string;
|
||||||
selectedApp?: AppName;
|
selectedApp?: AppName;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
mobileHide?: boolean;
|
mobileHide?: boolean;
|
||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
subscription: GlobalSubscription;
|
|
||||||
includeUnmanaged: boolean;
|
|
||||||
workspace: Workspace;
|
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