interface: convert local state to hooks

This commit is contained in:
Tyler Brown Cifu Shuster 2020-12-15 13:30:11 -08:00
parent be60e3b88f
commit 380a7b30af
49 changed files with 300 additions and 687 deletions

View File

@ -1693,9 +1693,9 @@
"integrity": "sha512-3OPSdf9cejP/TSzWXuBaYbzLtAfBzQnc75SlPLkoPfwpxnv1Bvy9hiWngLY0WnKRR6lMOldnkYQCCuNWeDibYQ=="
},
"@tlon/indigo-react": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@tlon/indigo-react/-/indigo-react-1.2.5.tgz",
"integrity": "sha512-NOQTwH74l/XXMIfQ4ZzymvZuk1WK1nmO552TmXrQxBUSb7HmdlA8anG5oRrvnLJTkajLCY59McLkDca+lCcvwg==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@tlon/indigo-react/-/indigo-react-1.2.15.tgz",
"integrity": "sha512-h9umWEzYZwyb53ujWoCQCJQwY9RUuoDaf6189+0LH3C7y9fybJe6vzbW6g2cUVH8dXA2EZkedS5nriYR0IpQbw==",
"requires": {
"@reach/menu-button": "^0.10.5",
"react": "^16.13.1",
@ -5681,6 +5681,11 @@
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true
},
"immer": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/immer/-/immer-8.0.0.tgz",
"integrity": "sha512-jm87NNBAIG4fHwouilCHIecFXp5rMGkiFrAuhVO685UnMAlOneEAnOyzPt8OnP47TC11q/E7vpzZe0WvwepFTg=="
},
"import-fresh": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
@ -12212,6 +12217,11 @@
"synchronous-promise": "^2.0.13",
"toposort": "^2.0.2"
}
},
"zustand": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-3.2.0.tgz",
"integrity": "sha512-MBYFrnUdgFVi38tdQNSzVN9cPpRDf7w2HhdHGDSgBRHN7vIbUGUR3aBdVQykelXzSFR7iVj3YNBuq7B9ceMI5w=="
}
}
}

View File

@ -18,6 +18,7 @@
"css-loader": "^3.5.3",
"file-saver": "^2.0.2",
"formik": "^2.1.4",
"immer": "^8.0.0",
"lodash": "^4.17.15",
"markdown-to-jsx": "^6.11.4",
"moment": "^2.20.1",
@ -41,7 +42,8 @@
"styled-system": "^5.1.5",
"suncalc": "^1.8.0",
"urbit-ob": "^5.0.0",
"yup": "^0.29.3"
"yup": "^0.29.3",
"zustand": "^3.2.0"
},
"devDependencies": {
"@babel/core": "^7.9.0",

View File

@ -1,6 +1,5 @@
import BaseApi from "./base";
import { StoreState } from "../store/type";
import { BackgroundConfig, LocalUpdateRemoteContentPolicy } from "../types/local-update";
export default class LocalApi extends BaseApi<StoreState> {
getBaseHash() {
@ -9,76 +8,6 @@ export default class LocalApi extends BaseApi<StoreState> {
});
}
sidebarToggle() {
this.store.handleEvent({
data: {
local: {
sidebarToggle: true
}
}
})
}
setDark(isDark: boolean) {
this.store.handleEvent({
data: {
local: {
setDark: isDark
}
}
});
}
setOmnibox() {
this.store.handleEvent({
data: {
local: {
omniboxShown: true
},
},
});
}
setBackground(backgroundConfig: BackgroundConfig) {
this.store.handleEvent({
data: {
local: {
backgroundConfig
}
}
});
}
hideAvatars(hideAvatars: boolean) {
this.store.handleEvent({
data: {
local: {
hideAvatars
}
}
});
}
hideNicknames(hideNicknames: boolean) {
this.store.handleEvent({
data: {
local: {
hideNicknames
}
}
});
}
setRemoteContentPolicy(policy: LocalUpdateRemoteContentPolicy) {
this.store.handleEvent({
data: {
local: {
remoteContentPolicy: policy
}
}
});
}
dehydrate() {
this.store.dehydrate();
}

View File

@ -1,7 +1,9 @@
import { useEffect } from 'react';
import _ from "lodash";
import f from "lodash/fp";
import f, { memoize } from "lodash/fp";
import bigInt, { BigInteger } from "big-integer";
import { Contact } from '~/types';
import useLocalState from '../state/local';
export const MOBILE_BROWSER_REGEX = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i;
@ -353,4 +355,9 @@ export function usePreventWindowUnload(shouldPreventDefault: boolean, message =
export function pluralize(text: string, isPlural = false, vowel = false) {
return isPlural ? `${text}s`: `${vowel ? 'an' : 'a'} ${text}`;
}
export function useShowNickname(contact: Contact | null): boolean {
const hideNicknames = useLocalState(state => state.hideNicknames);
return !!(contact && contact.nickname && !hideNicknames);
}

View File

@ -1,9 +1,9 @@
import _ from 'lodash';
import { StoreState } from '~/store/type';
import { Cage } from '~/types/cage';
import { LocalUpdate, BackgroundConfig } from '~/types/local-update';
import { LocalUpdate } from '~/types/local-update';
type LocalState = Pick<StoreState, 'sidebarShown' | 'omniboxShown' | 'baseHash' | 'hideAvatars' | 'hideNicknames' | 'background' | 'dark' | 'suspendedFocus' | 'remoteContentPolicy'>;
type LocalState = Pick<StoreState, 'baseHash'>;
export default class LocalReducer<S extends LocalState> {
rehydrate(state: S) {
@ -18,20 +18,11 @@ export default class LocalReducer<S extends LocalState> {
}
dehydrate(state: S) {
const json = _.pick(state, ['hideNicknames' , 'hideAvatars' , 'background', 'remoteContentPolicy']);
localStorage.setItem('localReducer', JSON.stringify(json));
}
reduce(json: Cage, state: S) {
const data = json['local'];
if (data) {
this.sidebarToggle(data, state);
this.setDark(data, state);
this.baseHash(data, state);
this.backgroundConfig(data, state)
this.hideAvatars(data, state)
this.hideNicknames(data, state)
this.omniboxShown(data, state);
this.remoteContentPolicy(data, state);
}
}
baseHash(obj: LocalUpdate, state: S) {
@ -39,53 +30,4 @@ export default class LocalReducer<S extends LocalState> {
state.baseHash = obj.baseHash;
}
}
omniboxShown(obj: LocalUpdate, state: S) {
if ('omniboxShown' in obj) {
state.omniboxShown = !state.omniboxShown;
if (state.suspendedFocus) {
state.suspendedFocus.focus();
state.suspendedFocus = null;
} else {
state.suspendedFocus = document.activeElement;
document.activeElement?.blur();
}
}
}
sidebarToggle(obj: LocalUpdate, state: S) {
if ('sidebarToggle' in obj) {
state.sidebarShown = !state.sidebarShown;
}
}
setDark(obj: LocalUpdate, state: S) {
if('setDark' in obj) {
state.dark = obj.setDark;
}
}
backgroundConfig(obj: LocalUpdate, state: S) {
if('backgroundConfig' in obj) {
state.background = obj.backgroundConfig;
}
}
remoteContentPolicy(obj: LocalUpdate, state: S) {
if('remoteContentPolicy' in obj) {
state.remoteContentPolicy = obj.remoteContentPolicy;
}
}
hideAvatars(obj: LocalUpdate, state: S) {
if('hideAvatars' in obj) {
state.hideAvatars = obj.hideAvatars;
}
}
hideNicknames(obj: LocalUpdate, state: S) {
if( 'hideNicknames' in obj) {
state.hideNicknames = obj.hideNicknames;
}
}
}

View File

@ -0,0 +1,58 @@
import React, { ReactNode } from "react";
import create, { State } from 'zustand';
import { persist } from 'zustand/middleware';
import produce from 'immer';
import { BackgroundConfig, RemoteContentPolicy } from "~/types/local-update";
export interface LocalState extends State {
hideAvatars: boolean;
hideNicknames: boolean;
remoteContentPolicy: RemoteContentPolicy;
dark: boolean;
background: BackgroundConfig;
omniboxShown: boolean;
suspendedFocus?: HTMLElement;
toggleOmnibox: () => void;
set: (fn: (state: LocalState) => void) => void
};
const useLocalState = create<LocalState>(persist((set, get) => ({
dark: false,
background: undefined,
hideAvatars: false,
hideNicknames: false,
remoteContentPolicy: {
imageShown: true,
audioShown: true,
videoShown: true,
oembedShown: true,
},
omniboxShown: false,
suspendedFocus: undefined,
toggleOmnibox: () => set(produce(state => {
state.omniboxShown = !state.omniboxShown;
if (state.suspendedFocus) {
state.suspendedFocus.focus();
state.suspendedFocus = undefined;
} else {
state.suspendedFocus = document.activeElement;
state.suspendedFocus.blur();
}
})),
set: fn => set(produce(fn))
}), {
name: 'localReducer'
}));
function withLocalState<P, S extends keyof LocalState>(Component: any, stateMemberKeys?: S[]) {
return React.forwardRef((props: Omit<P, S>, ref) => {
const localState = stateMemberKeys ? useLocalState(
state => stateMemberKeys.reduce(
(object, key) => ({ ...object, [key]: state[key] }), {}
)
): useLocalState();
return <Component ref={ref} {...localState} {...props} />
});
}
export { useLocalState as default, withLocalState };

View File

@ -51,19 +51,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
initialState(): StoreState {
return {
connection: 'connected',
sidebarShown: true,
omniboxShown: false,
suspendedFocus: null,
baseHash: null,
background: undefined,
remoteContentPolicy: {
imageShown: true,
audioShown: true,
videoShown: true,
oembedShown: true,
},
hideAvatars: false,
hideNicknames: false,
invites: {},
associations: {
contacts: {},
@ -88,7 +76,6 @@ export default class GlobalStore extends BaseStore<StoreState> {
credentials: null
},
contacts: {},
dark: false,
notifications: new BigIntOrderedMap<Timebox>(),
archivedNotifications: new BigIntOrderedMap<Timebox>(),
notificationsGroupConfig: [],

View File

@ -11,23 +11,13 @@ import {
Notifications,
NotificationGraphConfig,
GroupNotificationsConfig,
LocalUpdateRemoteContentPolicy,
BackgroundConfig,
Unreads
} from "~/types";
export interface StoreState {
// local state
sidebarShown: boolean;
omniboxShown: boolean;
suspendedFocus: HTMLInputElement | null;
dark: boolean;
connection: ConnectionStatus;
baseHash: string | null;
background: BackgroundConfig;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
hideAvatars: boolean;
hideNicknames: boolean;
// invite state
invites: Invites;

View File

@ -1,7 +1,3 @@
interface LocalUpdateSidebarToggle {
sidebarToggle: boolean;
}
interface LocalUpdateSetDark {
setDark: boolean;
}
@ -26,7 +22,7 @@ interface LocalUpdateSetOmniboxShown {
omniboxShown: boolean;
}
export interface LocalUpdateRemoteContentPolicy {
export interface RemoteContentPolicy {
imageShown: boolean;
audioShown: boolean;
videoShown: boolean;
@ -46,11 +42,10 @@ interface BackgroundConfigColor {
export type BackgroundConfig = BackgroundConfigUrl | BackgroundConfigColor | undefined;
export type LocalUpdate =
LocalUpdateSidebarToggle
| LocalUpdateSetDark
| LocalUpdateBaseHash
| LocalUpdateBackgroundConfig
| LocalUpdateHideAvatars
| LocalUpdateHideNicknames
| LocalUpdateSetOmniboxShown
| LocalUpdateRemoteContentPolicy;
| RemoteContentPolicy;

View File

@ -26,6 +26,7 @@ import GlobalSubscription from '~/logic/subscription/global';
import GlobalApi from '~/logic/api/global';
import { uxToHex } from '~/logic/lib/util';
import { foregroundFromBackground } from '~/logic/lib/sigil';
import { withLocalState } from '~/logic/state/local';
const Root = styled.div`
@ -86,23 +87,29 @@ class App extends React.Component {
componentDidMount() {
this.subscription.start();
this.themeWatcher = window.matchMedia('(prefers-color-scheme: dark)');
this.api.local.setDark(this.themeWatcher.matches);
this.themeWatcher.addListener(this.updateTheme);
this.themeWatcher.onchange = this.updateTheme;
setTimeout(() => {
// Something about how the store works doesn't like changing it
// before the app has actually rendered, hence the timeout
this.updateTheme(this.themeWatcher);
}, 500);
this.api.local.getBaseHash();
this.store.rehydrate();
Mousetrap.bindGlobal(['command+/', 'ctrl+/'], (e) => {
e.preventDefault();
e.stopImmediatePropagation();
this.api.local.setOmnibox();
this.props.toggleOmnibox();
});
}
componentWillUnmount() {
this.themeWatcher.removeListener(this.updateTheme);
this.themeWatcher.onchange = undefined;
}
updateTheme(e) {
this.api.local.setDark(e.matches);
this.props.set(state => {
state.dark = e.matches;
});
}
faviconString() {
@ -122,11 +129,11 @@ class App extends React.Component {
}
render() {
const { state } = this;
const { state, props } = this;
const associations = state.associations ?
state.associations : { contacts: {} };
const theme = state.dark ? dark : light;
const { background } = state;
const theme = props.dark ? dark : light;
const background = this.props.background;
const notificationsCount = state.notificationsCount || 0;
const doNotDisturb = state.doNotDisturb || false;
@ -164,7 +171,7 @@ class App extends React.Component {
notifications={state.notificationsCount}
invites={state.invites}
groups={state.groups}
show={state.omniboxShown}
show={this.props.omniboxShown}
/>
</ErrorBoundary>
<ErrorBoundary>
@ -183,5 +190,5 @@ class App extends React.Component {
}
}
export default process.env.NODE_ENV === 'production' ? App : hot(App);
export default withLocalState(process.env.NODE_ENV === 'production' ? App : hot(App));

View File

@ -87,7 +87,6 @@ export function ChatResource(props: ChatResourceProps) {
<Col {...bind} height="100%" overflow="hidden" position="relative">
{dragging && <SubmitDragger />}
<ChatWindow
remoteContentPolicy={props.remoteContentPolicy}
mailboxSize={5}
match={props.match as any}
stationPendingMessages={[]}
@ -105,8 +104,6 @@ export function ChatResource(props: ChatResourceProps) {
ship={owner}
station={station}
api={props.api}
hideNicknames={props.hideNicknames}
hideAvatars={props.hideAvatars}
location={props.location}
scrollTo={scrollTo ? parseInt(scrollTo, 10) : undefined}
/>
@ -119,7 +116,6 @@ export function ChatResource(props: ChatResourceProps) {
contacts={contacts}
onUnmount={appendUnsent}
s3={props.s3}
hideAvatars={props.hideAvatars}
placeholder="Message..."
message={unsent[station] || ''}
deleteMessage={clearUnsent}

View File

@ -10,6 +10,7 @@ import { Envelope } from '~/types/chat-update';
import { Contacts, Content } from '~/types';
import { Row, BaseImage, Box, Icon, LoadingSpinner } from '@tlon/indigo-react';
import withS3 from '~/views/components/withS3';
import { withLocalState } from '~/logic/state/local';
type ChatInputProps = IuseS3 & {
api: GlobalApi;
@ -66,7 +67,7 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
inCodeMode: false
}, async () => {
const output = await props.api.graph.eval(text);
const contents: Content[] = [{ code: { output, expression: text }}];
const contents: Content[] = [{ code: { output, expression: text }}];
const post = createPost(contents);
props.api.graph.addPost(ship, name, post);
});
@ -199,4 +200,4 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
}
}
export default withS3(ChatInput, {accept: 'image/*'});
export default withLocalState(withS3(ChatInput, {accept: 'image/*'}), ['hideAvatars']);

View File

@ -4,14 +4,14 @@ import _ from "lodash";
import { Box, Row, Text, Rule } from "@tlon/indigo-react";
import OverlaySigil from '~/views/components/OverlaySigil';
import { uxToHex, cite, writeText } from '~/logic/lib/util';
import { Envelope, IMessage } from "~/types/chat-update";
import { Group, Association, Contacts, LocalUpdateRemoteContentPolicy, Post } from "~/types";
import { uxToHex, cite, writeText, useShowNickname } from '~/logic/lib/util';
import { Group, Association, Contacts, Post } from "~/types";
import TextContent from './content/text';
import CodeContent from './content/code';
import RemoteContent from '~/views/components/RemoteContent';
import { Mention } from "~/views/components/MentionText";
import styled from "styled-components";
import useLocalState from "~/logic/state/local";
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
@ -39,9 +39,6 @@ interface ChatMessageProps {
group: Group;
association: Association;
contacts: Contacts;
hideAvatars: boolean;
hideNicknames: boolean;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
className?: string;
isPending: boolean;
style?: any;
@ -76,9 +73,6 @@ export default class ChatMessage extends Component<ChatMessageProps> {
group,
association,
contacts,
hideAvatars,
hideNicknames,
remoteContentPolicy,
className = '',
isPending,
style,
@ -109,11 +103,8 @@ export default class ChatMessage extends Component<ChatMessageProps> {
msg,
timestamp,
contacts,
hideNicknames,
association,
group,
hideAvatars,
remoteContentPolicy,
measure: reboundMeasure.bind(this),
style,
containerClass,
@ -162,9 +153,6 @@ interface MessageProps {
group: Group;
association: Association;
contacts: Contacts;
hideAvatars: boolean;
hideNicknames: boolean;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
containerClass: string;
isPending: boolean;
style: any;
@ -172,105 +160,96 @@ interface MessageProps {
scrollWindow: HTMLDivElement;
};
export class MessageWithSigil extends PureComponent<MessageProps> {
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
export const MessageWithSigil = (props) => {
const {
msg,
timestamp,
contacts,
association,
group,
measure,
api,
history,
scrollWindow,
fontSize
} = props;
render() {
const {
msg,
timestamp,
contacts,
hideNicknames,
association,
group,
hideAvatars,
remoteContentPolicy,
measure,
api,
history,
scrollWindow,
fontSize
} = this.props;
const dark = useLocalState(state => state.dark);
const datestamp = moment.unix(msg['time-sent'] / 1000).format(DATESTAMP_FORMAT);
const contact = msg.author in contacts ? contacts[msg.author] : false;
const showNickname = !hideNicknames && contact && contact.nickname;
const name = showNickname ? contact!.nickname : cite(msg.author);
const color = contact ? `#${uxToHex(contact.color)}` : this.isDark ? '#000000' :'#FFFFFF'
const sigilClass = contact ? '' : this.isDark ? 'mix-blend-diff' : 'mix-blend-darken';
const datestamp = moment.unix(msg['time-sent'] / 1000).format(DATESTAMP_FORMAT);
const contact = msg.author in contacts ? contacts[msg.author] : false;
const showNickname = useShowNickname(contact);
const name = showNickname ? contact.nickname : cite(msg.author);
const color = contact ? `#${uxToHex(contact.color)}` : dark ? '#000000' :'#FFFFFF'
const sigilClass = contact ? '' : dark ? 'mix-blend-diff' : 'mix-blend-darken';
let nameSpan = null;
let nameSpan = null;
const copyNotice = (saveName) => {
if (nameSpan !== null) {
nameSpan.innerText = 'Copied';
setTimeout(() => {
nameSpan.innerText = saveName;
}, 800);
}
};
const copyNotice = (saveName) => {
if (nameSpan !== null) {
nameSpan.innerText = 'Copied';
setTimeout(() => {
nameSpan.innerText = saveName;
}, 800);
}
};
return (
<>
<OverlaySigil
ship={msg.author}
contact={contact}
color={color}
sigilClass={sigilClass}
group={group}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
scrollWindow={scrollWindow}
history={history}
api={api}
bg="white"
className="fl pr3 v-top pt1"
/>
<Box flexGrow={1} display='block' className="clamp-message">
<Box
return (
<>
<OverlaySigil
ship={msg.author}
contact={contact}
color={color}
sigilClass={sigilClass}
group={group}
scrollWindow={scrollWindow}
history={history}
api={api}
bg="white"
className="fl pr3 v-top pt1"
/>
<Box flexGrow={1} display='block' className="clamp-message">
<Box
flexShrink={0}
className="hide-child"
pt={1}
pb={1}
display='flex'
alignItems='center'
>
<Text
fontSize={0}
mr={3}
flexShrink={0}
className="hide-child"
pt={1}
pb={1}
display='flex'
alignItems='center'
>
<Text
fontSize={0}
mr={3}
flexShrink={0}
mono={!showNickname}
fontWeight={(showNickname) ? '500' : '400'}
className={`mw5 db truncate pointer`}
ref={e => nameSpan = e}
onClick={() => {
writeText(`~${msg.author}`);
copyNotice(name);
}}
title={`~${msg.author}`}
>{name}</Text>
<Text flexShrink='0' gray mono className="v-mid">{timestamp}</Text>
<Text flexShrink={0} gray mono ml={2} className="v-mid child dn-s">{datestamp}</Text>
</Box>
<ContentBox flexShrink={0} fontSize={fontSize ? fontSize : '14px'}>
{msg.contents.map(c =>
<MessageContent
contacts={contacts}
content={c}
remoteContentPolicy={remoteContentPolicy}
measure={measure}
fontSize={fontSize}
group={group}
hideNicknames={hideNicknames}
hideAvatars={hideAvatars}
/>)}
</ContentBox>
mono={!showNickname}
fontWeight={(showNickname) ? '500' : '400'}
className={`mw5 db truncate pointer`}
ref={e => nameSpan = e}
onClick={() => {
writeText(`~${msg.author}`);
copyNotice(name);
}}
title={`~${msg.author}`}
>{name}</Text>
<Text flexShrink='0' gray mono className="v-mid">{timestamp}</Text>
<Text flexShrink={0} gray mono ml={2} className="v-mid child dn-s">{datestamp}</Text>
</Box>
</>
);
}
<ContentBox flexShrink={0} fontSize={fontSize ? fontSize : '14px'}>
{msg.contents.map(c =>
<MessageContent
contacts={contacts}
content={c}
measure={measure}
fontSize={fontSize}
group={group}
/>)}
</ContentBox>
</Box>
</>
);
}
const ContentBox = styled(Box)`
& > :first-child {
margin-left: 0px;
@ -278,7 +257,7 @@ const ContentBox = styled(Box)`
`;
export const MessageWithoutSigil = ({ timestamp, contacts, msg, remoteContentPolicy, measure, group, hideNicknames, hideAvatars }) => (
export const MessageWithoutSigil = ({ timestamp, contacts, msg, measure, group }) => (
<>
<Text flexShrink={0} mono gray display='inline-block' pt='2px' lineHeight='tall' className="child">{timestamp}</Text>
<ContentBox flexShrink={0} fontSize='14px' className="clamp-message" style={{ flexGrow: 1 }}>
@ -288,15 +267,12 @@ export const MessageWithoutSigil = ({ timestamp, contacts, msg, remoteContentPol
contacts={contacts}
content={c}
group={group}
remoteContentPolicy={remoteContentPolicy}
measure={measure}
hideNicknames={hideNicknames}
hideAvatars={hideAvatars}/>))}
measure={measure}/>))}
</ContentBox>
</>
);
export const MessageContent = ({ content, contacts, remoteContentPolicy, measure, fontSize, group, hideNicknames, hideAvatars }) => {
export const MessageContent = ({ content, contacts, measure, fontSize, group }) => {
if ('code' in content) {
return <CodeContent content={content} />;
} else if ('url' in content) {
@ -304,7 +280,6 @@ export const MessageContent = ({ content, contacts, remoteContentPolicy, measure
<Box mx="2px" flexShrink={0} fontSize={fontSize ? fontSize : '14px'} lineHeight="tall" color='black'>
<RemoteContent
url={content.url}
remoteContentPolicy={remoteContentPolicy}
onLoad={measure}
imageProps={{style: {
maxWidth: '18rem',
@ -325,7 +300,7 @@ export const MessageContent = ({ content, contacts, remoteContentPolicy, measure
} else if ('text' in content) {
return <TextContent fontSize={fontSize} content={content} />;
} else if ('mention' in content) {
return <Mention group={group} ship={content.mention} contact={contacts?.[content.mention]} hideNicknames={hideNicknames} hideAvatars={hideAvatars} />
return <Mention group={group} ship={content.mention} contact={contacts?.[content.mention]} />
} else {
return null;
}

View File

@ -9,8 +9,7 @@ import { Contacts } from "~/types/contact-update";
import { Association } from "~/types/metadata-update";
import { Group } from "~/types/group-update";
import { Envelope, IMessage } from "~/types/chat-update";
import { LocalUpdateRemoteContentPolicy, Graph } from "~/types";
import { BigIntOrderedMap } from "~/logic/lib/BigIntOrderedMap";
import { Graph } from "~/types";
import VirtualScroller from "~/views/components/VirtualScroller";
@ -41,9 +40,6 @@ type ChatWindowProps = RouteComponentProps<{
ship: Patp;
station: any;
api: GlobalApi;
hideNicknames: boolean;
hideAvatars: boolean;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
scrollTo?: number;
}
@ -253,16 +249,13 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
contacts,
mailboxSize,
graph,
hideAvatars,
hideNicknames,
remoteContentPolicy,
history
} = this.props;
const unreadMarkerRef = this.unreadMarkerRef;
const messageProps = { association, group, contacts, hideAvatars, hideNicknames, remoteContentPolicy, unreadMarkerRef, history, api };
const messageProps = { association, group, contacts, unreadMarkerRef, history, api };
const keys = graph.keys().reverse();
const unreadIndex = keys[this.props.unreadCount];

View File

@ -14,10 +14,7 @@ export default class GraphApp extends PureComponent {
const graphKeys = props.graphKeys || new Set([]);
const graphs = props.graphs || {};
const {
api, sidebarShown, s3,
hideAvatars, hideNicknames, remoteContentPolicy
} = this.props;
const { api } = this.props;
return (
<Switch>

View File

@ -10,7 +10,6 @@ import { RouteComponentProps } from "react-router-dom";
import { LinkItem } from "./components/LinkItem";
import LinkSubmit from "./components/LinkSubmit";
import { LinkPreview } from "./components/link-preview";
import { Comments } from "~/views/components/Comments";
import "./css/custom.css";
@ -33,9 +32,6 @@ export function LinkResource(props: LinkResourceProps) {
graphKeys,
unreads,
s3,
hideAvatars,
hideNicknames,
remoteContentPolicy,
history
} = props;
@ -85,9 +81,6 @@ export function LinkResource(props: LinkResourceProps) {
contacts={contactDetails}
unreads={unreads}
nickname={contact?.nickname}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
remoteContentPolicy={remoteContentPolicy}
baseUrl={resourceUrl}
group={group}
path={resource["group-path"]}
@ -126,9 +119,6 @@ export function LinkResource(props: LinkResourceProps) {
key={node.post.index}
resource={resourcePath}
node={node}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
remoteContentPolicy={remoteContentPolicy}
baseUrl={resourceUrl}
unreads={unreads}
group={group}
@ -145,9 +135,6 @@ export function LinkResource(props: LinkResourceProps) {
unreads={unreads}
contacts={contactDetails}
api={api}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
remoteContentPolicy={remoteContentPolicy}
editCommentId={editCommentId}
history={props.history}
baseUrl={`${resourceUrl}/${props.match.params.index}`}

View File

@ -1,13 +1,12 @@
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { Row, Col, Anchor, Box, Text, BaseImage, Icon, Action } from '@tlon/indigo-react';
import { Row, Col, Anchor, Box, Text, Icon, Action } from '@tlon/indigo-react';
import { Sigil } from '~/logic/lib/sigil';
import { writeText } from '~/logic/lib/util';
import Author from '~/views/components/Author';
import { roleForShip } from '~/logic/lib/group';
import { Contacts, GraphNode, Group, LocalUpdateRemoteContentPolicy, Rolodex, Unreads } from '~/types';
import { Contacts, GraphNode, Group, Rolodex, Unreads } from '~/types';
import GlobalApi from '~/logic/api/global';
import { Dropdown } from '~/views/components/Dropdown';
import RemoteContent from '~/views/components/RemoteContent';
@ -15,9 +14,6 @@ import RemoteContent from '~/views/components/RemoteContent';
interface LinkItemProps {
node: GraphNode;
resource: string;
hideAvatars: boolean;
hideNicknames: boolean;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
api: GlobalApi;
group: Group;
path: string;
@ -29,9 +25,6 @@ export const LinkItem = (props: LinkItemProps) => {
const {
node,
resource,
hideAvatars,
hideNicknames,
remoteContentPolicy,
api,
group,
path,
@ -97,7 +90,6 @@ export const LinkItem = (props: LinkItemProps) => {
<RemoteContent
url={contents[1].url}
text={contents[0].text}
remoteContentPolicy={remoteContentPolicy}
unfold={true}
style={{ alignSelf: 'center' }}
oembedProps={{
@ -130,12 +122,9 @@ export const LinkItem = (props: LinkItemProps) => {
<Author
showImage
contacts={contacts[path]}
contacts={contacts}
ship={author}
date={node.post['time-sent']}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
remoteContentPolicy={remoteContentPolicy}
group={group}
api={api}
></Author>

View File

@ -1,84 +0,0 @@
import React from 'react';
import { Row, Col, Anchor, Box, Text, BaseImage } from '@tlon/indigo-react';
import { Sigil } from '~/logic/lib/sigil';
import { Link } from 'react-router-dom';
import { cite } from '~/logic/lib/util';
import { Author } from "~/views/apps/publish/components/Author";
import { roleForShip } from '~/logic/lib/group';
export const LinkItem = (props) => {
const {
node,
nickname,
avatar,
contacts,
unread,
resource,
hideAvatars,
hideNicknames,
api,
group
} = props;
const URLparser = new RegExp(
/((?:([\w\d\.-]+)\:\/\/?){1}(?:(www)\.?){0,1}(((?:[\w\d-]+\.)*)([\w\d-]+\.[\w\d]+))){1}(?:\:(\d+)){0,1}((\/(?:(?:[^\/\s\?]+\/)*))(?:([^\?\/\s#]+?(?:.[^\?\s]+){0,1}){0,1}(?:\?([^\s#]+)){0,1})){0,1}(?:#([^#\s]+)){0,1}/
);
const author = node.post.author;
const index = node.post.index.split('/')[1];
const size = node.children ? node.children.size : 0;
const date = node.post['time-sent'];
const contents = node.post.contents;
const hostname = URLparser.exec(contents[1].url) ? URLparser.exec(contents[1].url)[4] : null;
const showAvatar = avatar && !hideAvatars;
const showNickname = nickname && !hideNicknames;
const img = showAvatar
? <BaseImage display='inline-block' src={props.avatar} height={36} width={36} />
: <Sigil ship={`~${author}`} size={36} color={'#' + props.color} />;
const baseUrl = props.baseUrl || `/~404/${resource}`;
const ourRole = group ? roleForShip(group, window.ship) : undefined;
const [ship, name] = resource.split('/');
return (
<Row minWidth='0' flexShrink='0' width="100%" alignItems="center" py={3} bg="white">
{img}
<Col minWidth='0' height="100%" width='100%' justifyContent="space-between" ml={2}>
<Anchor
lineHeight="tall"
display='flex'
style={{ textDecoration: 'none' }}
href={contents[1].url}
width="100%"
target="_blank"
rel="noopener noreferrer"
>
<Text display='inline-block' overflow='hidden' style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }}>{contents[0].text}</Text>
<Text ml="2" color="gray" display='inline-block' flexShrink='0'>{hostname} </Text>
</Anchor>
<Row alignItems="center" width="100%">
<Author
contacts={contacts}
ship={author}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
unread={unread}
date={date}
>
<Link to={`${baseUrl}/${index}`}>
<Text ml="2" color="gray">{size} comments</Text>
</Link>
{(ourRole === 'admin' || node.post.author === window.ship)
&& (<Text color='red' ml='2' cursor='pointer' onClick={() => api.graph.removeNodes(`~${ship}`, name, [node.post.index])}>Delete</Text>)}
</Author>
</Row>
</Col>
</Row>
);
};

View File

@ -1,70 +0,0 @@
import React, { useEffect } from 'react';
import { cite } from '~/logic/lib/util';
import RemoteContent from '~/views/components/RemoteContent';
import { Box, Col, Anchor, Text } from '@tlon/indigo-react';
import moment from 'moment';
const URLparser = new RegExp(
/((?:([\w\d\.-]+)\:\/\/?){1}(?:(www)\.?){0,1}(((?:[\w\d-]+\.)*)([\w\d-]+\.[\w\d]+))){1}(?:\:(\d+)){0,1}((\/(?:(?:[^\/\s\?]+\/)*))(?:([^\?\/\s#]+?(?:.[^\?\s]+){0,1}){0,1}(?:\?([^\s#]+)){0,1})){0,1}(?:#([^#\s]+)){0,1}/
);
export const LinkPreview = (props) => {
const showNickname = props.nickname && !props.hideNicknames;
const author = props.post.author;
const title = props.post.contents[0].text;
const url = props.post.contents[1].url;
const hostname = URLparser.exec(url) ? URLparser.exec(url)[4] : null;
const timeSent =
moment.unix(props.post['time-sent'] / 1000).format('hh:mm a');
useEffect(() => {
return () => props.api.hark.markEachAsRead(props.association, '/', props.post.index, 'link', 'link');
}, [props.association, props.post.index]);
const embed = (
<RemoteContent
unfold={true}
renderUrl={false}
url={url}
remoteContentPolicy={props.remoteContentPolicy}
className="mw-100"
/>
);
return (
<Box pb='6' width='100%'>
<Box width='100%' textAlign='center'>{embed}</Box>
<Col flex='1 1 auto' minWidth='0' minHeight='0' pt='6'>
<Anchor href={url}
lineHeight="tall"
display='flex'
style={{ textDecoration: 'none' }}
width='100%'
target="_blank"
rel="noopener noreferrer">
<Text
display='inline-block'
overflow='hidden'
style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }}
>
{title}
</Text>
<Text ml="2" color="gray" display='inline-block' flexShrink='0'>{hostname} </Text>
</Anchor>
<Box width='100%' pt='1'>
<Text fontSize='0' pr='2' display='inline-block' mono={!showNickname} title={author}>
{showNickname ? props.nickname : cite(`~${author}`)}
</Text>
<Text fontSize='0' gray pr='3' display='inline-block'>{timeSent}</Text>
<Text gray fontSize='0' display='inline-block'>
{props.commentNumber} comments
</Text>
</Box>
</Col>
</Box>
);
};

View File

@ -34,9 +34,8 @@ export function ChatNotification(props: {
contacts: Rolodex;
groups: Groups;
api: GlobalApi;
remoteContentPolicy: any;
}) {
const { index, contents, read, time, api, timebox, remoteContentPolicy } = props;
const { index, contents, read, time, api, timebox } = props;
const authors = _.map(contents, "author");
const { chat, mention } = index;
@ -90,7 +89,6 @@ export function ChatNotification(props: {
contacts={groupContacts}
fontSize='0'
pt='2'
remoteContentPolicy={remoteContentPolicy}
/>
</Link>
);

View File

@ -80,7 +80,6 @@ const GraphNodeContent = ({ group, post, contacts, mod, description, index, remo
content={contents}
contacts={contacts}
group={group}
remoteContentPolicy={remoteContentPolicy}
/>
}
return null;
@ -91,7 +90,6 @@ const GraphNodeContent = ({ group, post, contacts, mod, description, index, remo
content={contents}
group={group}
contacts={contacts}
remoteContentPolicy={remoteContentPolicy}
/>
} else if (idx[1] === "1") {
const [{ text: header }, { text: body }] = contents;
@ -132,7 +130,6 @@ const GraphNodeContent = ({ group, post, contacts, mod, description, index, remo
msg={post}
fontSize='0'
pt='2'
remoteContentPolicy={remoteContentPolicy}
/>
</Row>);
@ -219,7 +216,6 @@ const GraphNode = ({
mod={mod}
description={description}
index={index}
remoteContentPolicy={remoteContentPolicy}
group={group}
/>
</Row>
@ -239,9 +235,8 @@ export function GraphNotification(props: {
groups: Groups;
contacts: Rolodex;
api: GlobalApi;
remoteContentPolicy: any;
}) {
const { contents, index, read, time, api, timebox, remoteContentPolicy, groups } = props;
const { contents, index, read, time, api, timebox, groups } = props;
const authors = _.map(contents, "author");
const { graph, group } = index;
@ -287,7 +282,6 @@ return (
groupPath={group}
read={read}
onRead={onClick}
remoteContentPolicy={remoteContentPolicy}
/>
))}
</Col>

View File

@ -4,7 +4,7 @@ import f from "lodash/fp";
import _ from "lodash";
import moment from "moment";
import { PropFunc } from "~/types/util";
import { getContactDetails } from "~/logic/lib/util";
import { getContactDetails, useShowNickname } from "~/logic/lib/util";
import { Associations, Contact, Contacts, Rolodex } from "~/types";
const Text = (props: PropFunc<typeof Text>) => (
@ -14,7 +14,7 @@ const Text = (props: PropFunc<typeof Text>) => (
function Author(props: { patp: string; contacts: Contacts; last?: boolean }) {
const contact: Contact | undefined = props.contacts?.[props.patp];
const showNickname = !!contact?.nickname;
const showNickname = useShowNickname(contact);
const name = contact?.nickname || `~${props.patp}`;
return (

View File

@ -144,7 +144,6 @@ export default function Inbox(props: {
graphConfig={props.notificationsGraphConfig}
groupConfig={props.notificationsGroupConfig}
chatConfig={props.notificationsChatConfig}
remoteContentPolicy={props.remoteContentPolicy}
api={api}
/>
)}
@ -164,7 +163,6 @@ export default function Inbox(props: {
graphConfig={props.notificationsGraphConfig}
groupConfig={props.notificationsGroupConfig}
chatConfig={props.notificationsChatConfig}
remoteContentPolicy={props.remoteContentPolicy}
/>
)
)}
@ -230,7 +228,6 @@ function DaySection({
contacts={contacts}
groups={groups}
time={date}
remoteContentPolicy={remoteContentPolicy}
/>
</React.Fragment>
))

View File

@ -31,7 +31,6 @@ interface NotificationProps {
graphConfig: NotificationGraphConfig;
groupConfig: GroupNotificationsConfig;
chatConfig: string[];
remoteContentPolicy: any;
}
function getMuted(
@ -143,7 +142,6 @@ export function Notification(props: NotificationProps) {
timebox={props.time}
time={time}
associations={associations}
remoteContentPolicy={props.remoteContentPolicy}
/>
</Wrapper>
);
@ -184,7 +182,6 @@ export function Notification(props: NotificationProps) {
timebox={props.time}
time={time}
associations={associations}
remoteContentPolicy={props.remoteContentPolicy}
/>
</Wrapper>
);

View File

@ -12,6 +12,7 @@ import GlobalApi from '~/logic/api/global';
import { uxToHex } from '~/logic/lib/util';
import { S3State, BackgroundConfig } from '~/types';
import { BackgroundPicker, BgType } from './BackgroundPicker';
import useLocalState, { LocalState } from '~/logic/state/local';
const formSchema = Yup.object().shape({
bgType: Yup.string()
@ -33,15 +34,13 @@ interface FormSchema {
interface DisplayFormProps {
api: GlobalApi;
dark: boolean;
background: BackgroundConfig;
hideAvatars: boolean;
hideNicknames: boolean;
s3: S3State;
}
export default function DisplayForm(props: DisplayFormProps) {
const { api, background, hideAvatars, hideNicknames, s3 } = props;
const { api, s3 } = props;
const { hideAvatars, hideNicknames, background, set: setLocalState } = useLocalState();
let bgColor, bgUrl;
if (background?.type === 'url') {
@ -72,10 +71,11 @@ export default function DisplayForm(props: DisplayFormProps) {
? { type: 'url', url: values.bgUrl || '' }
: undefined;
api.local.setBackground(bgConfig);
api.local.hideAvatars(values.avatars);
api.local.hideNicknames(values.nicknames);
api.local.dehydrate();
setLocalState((state: LocalState) => {
state.background = bgConfig;
state.hideAvatars = values.avatars;
state.hideNicknames = values.nicknames;
});
actions.setSubmitting(false);
}}
>

View File

@ -8,7 +8,7 @@ import { Formik, Form } from "formik";
import * as Yup from "yup";
import GlobalApi from "~/logic/api/global";
import { LocalUpdateRemoteContentPolicy } from "~/types/local-update";
import useLocalState from "~/logic/state/local";
const formSchema = Yup.object().shape({
imageShown: Yup.boolean(),
@ -26,11 +26,12 @@ interface FormSchema {
interface RemoteContentFormProps {
api: GlobalApi;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
}
export default function RemoteContentForm(props: RemoteContentFormProps) {
const { api, remoteContentPolicy } = props;
const { api } = props;
const remoteContentPolicy = useLocalState(state => state.remoteContentPolicy);
const setRemoteContentPolicy = useLocalState(state => state.set);
const imageShown = remoteContentPolicy.imageShown;
const audioShown = remoteContentPolicy.audioShown;
const videoShown = remoteContentPolicy.videoShown;
@ -47,13 +48,9 @@ export default function RemoteContentForm(props: RemoteContentFormProps) {
} as FormSchema
}
onSubmit={(values, actions) => {
api.local.setRemoteContentPolicy({
imageShown: values.imageShown,
audioShown: values.audioShown,
videoShown: values.videoShown,
oembedShown: values.oembedShown,
setRemoteContentPolicy(state => {
Object.assign(state.remoteContentPolicy, values);
});
api.local.dehydrate();
actions.setSubmitting(false);
}}
>

View File

@ -13,12 +13,7 @@ type ProfileProps = StoreState & { api: GlobalApi; ship: string };
export default function Settings({
api,
s3,
dark,
hideAvatars,
hideNicknames,
background,
remoteContentPolicy
s3
}: ProfileProps) {
return (
<Box
@ -32,13 +27,9 @@ export default function Settings({
>
<DisplayForm
api={api}
dark={dark}
hideNicknames={hideNicknames}
hideAvatars={hideAvatars}
background={background}
s3={s3}
/>
<RemoteContentForm {...{ api, remoteContentPolicy }} />
<RemoteContentForm api={api} />
<S3Form api={api} s3={s3} />
<SecuritySettings api={api} />
</Box>

View File

@ -9,6 +9,7 @@ import { uxToHex, MOBILE_BROWSER_REGEX } from "~/logic/lib/util";
import Settings from "./components/settings";
import { ContactCard } from "~/views/landscape/components/ContactCard";
import useLocalState from "~/logic/state/local";
const SidebarItem = ({ children, view, current }) => {
const selected = current === view;
@ -42,6 +43,7 @@ const SidebarItem = ({ children, view, current }) => {
export default function ProfileScreen(props: any) {
const { ship, dark } = props;
const hideAvatars = useLocalState(state => state.hideAvatars);
return (
<>
<Helmet defer={false}>
@ -65,7 +67,7 @@ export default function ProfileScreen(props: any) {
history.replace("/~profile/identity");
}
const image = (!props?.hideAvatars && contact?.avatar)
const image = (!hideAvatars && contact?.avatar)
? <BaseImage src={contact.avatar} width='100%' height='100%' style={{ objectFit: 'cover' }} />
: <Sigil ship={`~${ship}`} size={80} color={sigilColor} />;
return (
@ -132,8 +134,6 @@ export default function ProfileScreen(props: any) {
path="/~/default"
api={props.api}
s3={props.s3}
hideAvatars={props.hideAvatars}
hideNicknames={props.hideNicknames}
/>
</>
)}

View File

@ -35,10 +35,7 @@ export function PublishResource(props: PublishResourceProps) {
history={props.history}
match={props.match}
location={props.location}
hideAvatars={props.hideAvatars}
unreads={props.unreads}
hideNicknames={props.hideNicknames}
remoteContentPolicy={props.remoteContentPolicy}
graphs={props.graphs}
s3={props.s3}
/>

View File

@ -10,7 +10,7 @@ import { NoteNavigation } from "./NoteNavigation";
import GlobalApi from "~/logic/api/global";
import { getLatestRevision, getComments } from '~/logic/lib/publish';
import Author from "~/views/components/Author";
import { Contacts, GraphNode, Graph, LocalUpdateRemoteContentPolicy, Association, Unreads, Group } from "~/types";
import { Contacts, GraphNode, Graph, Association, Unreads, Group } from "~/types";
interface NoteProps {
ship: string;
@ -21,9 +21,6 @@ interface NoteProps {
notebook: Graph;
contacts: Contacts;
api: GlobalApi;
hideAvatars: boolean;
hideNicknames: boolean;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
rootUrl: string;
baseUrl: string;
group: Group;
@ -95,8 +92,6 @@ export function Note(props: NoteProps & RouteComponentProps) {
<Text display="block" mb={2}>{title || ""}</Text>
<Box display="flex">
<Author
hideNicknames={props?.hideNicknames}
hideAvatars={props?.hideAvatars}
ship={post?.author}
contacts={contacts}
date={post?.["time-sent"]}
@ -121,9 +116,6 @@ export function Note(props: NoteProps & RouteComponentProps) {
contacts={props.contacts}
association={props.association}
api={props.api}
hideNicknames={props.hideNicknames}
hideAvatars={props.hideAvatars}
remoteContentPolicy={props.remoteContentPolicy}
baseUrl={baseUrl}
editCommentId={editCommentId}
history={props.history}

View File

@ -19,8 +19,6 @@ interface NotePreviewProps {
host: string;
book: string;
node: GraphNode;
hideAvatars?: boolean;
hideNicknames?: boolean;
baseUrl: string;
unreads: Unreads;
contacts: Contacts;
@ -33,7 +31,7 @@ const WrappedBox = styled(Box)`
`;
export function NotePreview(props: NotePreviewProps) {
const { node, contacts, hideAvatars, hideNicknames, group } = props;
const { node, contacts, group } = props;
const { post } = node;
if (!post) {
return null;
@ -84,8 +82,6 @@ export function NotePreview(props: NotePreviewProps) {
contacts={contacts}
ship={post?.author}
date={post?.['time-sent']}
hideAvatars={hideAvatars || false}
hideNicknames={hideNicknames || false}
group={group}
unread={isUnread}
api={props.api}

View File

@ -6,7 +6,7 @@ import { RouteComponentProps } from "react-router-dom";
import Note from "./Note";
import { EditPost } from "./EditPost";
import { GraphNode, Graph, Contacts, LocalUpdateRemoteContentPolicy, Association, S3State, Group } from "~/types";
import { GraphNode, Graph, Contacts, Association, S3State, Group } from "~/types";
interface NoteRoutesProps {
ship: string;
@ -16,9 +16,6 @@ interface NoteRoutesProps {
notebook: Graph;
contacts: Contacts;
api: GlobalApi;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
hideNicknames: boolean;
hideAvatars: boolean;
association: Association;
baseUrl?: string;
rootUrl?: string;

View File

@ -1,13 +1,10 @@
import React, { PureComponent } from "react";
import { Link, RouteComponentProps, Route, Switch } from "react-router-dom";
import React from "react";
import { Link, RouteComponentProps } from "react-router-dom";
import { NotebookPosts } from "./NotebookPosts";
import { roleForShip } from "~/logic/lib/group";
import { Box, Button, Text, Row, Col } from "@tlon/indigo-react";
import GlobalApi from "~/logic/api/global";
import styled from "styled-components";
import { Contacts, Rolodex, Groups, Associations, Graph, Association, Unreads } from "~/types";
import { deSig } from "~/logic/lib/util";
import { StatelessAsyncButton } from "~/views/components/StatelessAsyncButton";
import { useShowNickname } from "~/logic/lib/util";
interface NotebookProps {
api: GlobalApi;
@ -19,26 +16,17 @@ interface NotebookProps {
associations: Associations;
contacts: Rolodex;
groups: Groups;
hideNicknames: boolean;
hideAvatars: boolean;
baseUrl: string;
rootUrl: string;
unreads: Unreads;
}
interface NotebookState {
isUnsubscribing: boolean;
tab: string;
}
export function Notebook(props: NotebookProps & RouteComponentProps) {
const {
ship,
book,
notebookContacts,
groups,
hideNicknames,
hideAvatars,
association,
graph
} = props;
@ -46,7 +34,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
const group = groups[association?.['group-path']];
if (!group) {
return null; // Waitin on groups to populate
return null; // Waiting on groups to populate
}
const relativePath = (p: string) => props.baseUrl + p;
@ -59,7 +47,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
isWriter = isOwn || group.tags?.publish?.[`writers-${book}`]?.has(window.ship);
}
const showNickname = contact?.nickname && !hideNicknames;
const showNickname = useShowNickname(contact);
return (
<Col gapY="4" pt={4} mx="auto" px={3} maxWidth="768px">
@ -85,9 +73,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
host={ship}
book={book}
contacts={notebookContacts ? notebookContacts : {}}
hideNicknames={hideNicknames}
unreads={props.unreads}
hideAvatars={hideAvatars}
baseUrl={props.baseUrl}
api={props.api}
group={group}

View File

@ -8,13 +8,11 @@ import {
Groups,
Contacts,
Rolodex,
LocalUpdateRemoteContentPolicy,
Unreads,
S3State
} from "~/types";
import { Center, LoadingSpinner } from "@tlon/indigo-react";
import { Notebook as INotebook } from "~/types/publish-update";
import bigInt, { BigInteger } from 'big-integer';
import bigInt from 'big-integer';
import Notebook from "./Notebook";
import NewPost from "./new-post";
@ -33,10 +31,7 @@ interface NotebookRoutesProps {
groups: Groups;
baseUrl: string;
rootUrl: string;
hideAvatars: boolean;
hideNicknames: boolean;
association: Association;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
associations: Associations;
s3: S3State;
}
@ -116,9 +111,6 @@ export function NotebookRoutes(
noteId={noteIdNum}
contacts={notebookContacts}
association={props.association}
hideAvatars={props.hideAvatars}
hideNicknames={props.hideNicknames}
remoteContentPolicy={props.remoteContentPolicy}
group={group}
s3={props.s3}
{...routeProps}

View File

@ -2,7 +2,7 @@ import React, {ReactNode} from "react";
import moment from "moment";
import { Row, Box } from "@tlon/indigo-react";
import { uxToHex, cite } from "~/logic/lib/util";
import { uxToHex, cite, useShowNickname } from "~/logic/lib/util";
import { Contacts, Rolodex } from "~/types/contact-update";
import OverlaySigil from "./OverlaySigil";
import { Group, Association } from "~/types";
@ -14,8 +14,6 @@ interface AuthorProps {
ship: string;
date: number;
showImage?: boolean;
hideAvatars: boolean;
hideNicknames: boolean;
children?: ReactNode;
unread?: boolean;
group: Group;
@ -23,16 +21,16 @@ interface AuthorProps {
}
export default function Author(props: AuthorProps) {
const { contacts, ship = '', date, showImage, hideAvatars, hideNicknames, group, api } = props;
const { contacts, ship = '', date, showImage, group, api } = props;
const history = useHistory();
let contact;
if (contacts) {
contact = ship in contacts ? contacts[ship] : null;
}
const color = contact?.color ? `#${uxToHex(contact?.color)}` : "#000000";
const showNickname = !props.hideNicknames && contact?.nickname;
const showNickname = useShowNickname(contact);
const name = showNickname ? contact?.nickname : cite(ship);
const name = showNickname ? contact.nickname : cite(ship);
const dateFmt = moment(date).fromNow();
return (
<Row alignItems="center" width="auto">
@ -44,8 +42,6 @@ export default function Author(props: AuthorProps) {
color={color}
sigilClass={''}
group={group}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
history={history}
api={api}
bg="white"

View File

@ -7,7 +7,7 @@ import styled from 'styled-components';
import Author from '~/views/components/Author';
import { GraphNode, TextContent } from '~/types/graph-update';
import tokenizeMessage from '~/logic/lib/tokenizeMessage';
import { LocalUpdateRemoteContentPolicy, Group } from '~/types';
import { Group } from '~/types';
import { MentionText } from '~/views/components/MentionText';
import { getLatestCommentRevision } from '~/logic/lib/publish';
@ -25,14 +25,11 @@ interface CommentItemProps {
name: string;
ship: string;
api: GlobalApi;
hideNicknames: boolean;
hideAvatars: boolean;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
group: Group;
}
export function CommentItem(props: CommentItemProps) {
const { ship, contacts, name, api, remoteContentPolicy, comment, group } = props;
const { ship, contacts, name, api, comment, group } = props;
const [revNum, post] = getLatestCommentRevision(comment);
const disabled = props.pending || window.ship !== post?.author;
@ -53,9 +50,6 @@ export function CommentItem(props: CommentItemProps) {
ship={post?.author}
date={post?.['time-sent']}
unread={props.unread}
hideAvatars={props.hideAvatars}
hideNicknames={props.hideNicknames}
remoteContentPolicy={remoteContentPolicy}
group={group}
api={api}
>
@ -81,9 +75,6 @@ export function CommentItem(props: CommentItemProps) {
contacts={contacts}
group={group}
content={post?.contents}
remoteContentPolicy={remoteContentPolicy}
hideNicknames={props.hideNicknames}
hideAvatars={props.hideAvatars}
/>
</Box>
</Box>

View File

@ -6,7 +6,7 @@ import CommentInput from './CommentInput';
import { Contacts } from '~/types/contact-update';
import GlobalApi from '~/logic/api/global';
import { FormikHelpers } from 'formik';
import { Group, GraphNode, LocalUpdateRemoteContentPolicy, Unreads, Association } from '~/types';
import { Group, GraphNode, Association } from '~/types';
import { createPost, createBlankNodeWithChildPost } from '~/logic/api/graph';
import { getLatestCommentRevision } from '~/logic/lib/publish';
import { scanForMentions } from '~/logic/lib/graph';
@ -21,9 +21,6 @@ interface CommentsProps {
baseUrl: string;
contacts: Contacts;
api: GlobalApi;
hideAvatars: boolean;
hideNicknames: boolean;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
group: Group;
}
@ -126,9 +123,6 @@ export function Comments(props: CommentsProps) {
name={name}
ship={ship}
unread={i >= readCount}
hideNicknames={props.hideNicknames}
hideAvatars={props.hideAvatars}
remoteContentPolicy={props.remoteContentPolicy}
baseUrl={props.baseUrl}
group={group}
pending={idx.toString() === props.editCommentId}

View File

@ -5,25 +5,21 @@ import {
Contact,
Contacts,
Content,
LocalUpdateRemoteContentPolicy,
Group,
} from "~/types";
import RichText from "~/views/components/RichText";
import { cite, uxToHex } from "~/logic/lib/util";
import { ProfileOverlay } from "./ProfileOverlay";
import {useHistory} from "react-router-dom";
import { cite, useShowNickname, uxToHex } from "~/logic/lib/util";
import ProfileOverlay from "./ProfileOverlay";
import { useHistory } from "react-router-dom";
interface MentionTextProps {
contact?: Contact;
contacts?: Contacts;
content: Content[];
group: Group;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
hideNicknames: boolean;
hideAvatars: boolean;
}
export function MentionText(props: MentionTextProps) {
const { content, contacts, contact, group, hideNicknames, hideAvatars } = props;
const { content, contacts, contact, group } = props;
return (
<>
@ -33,14 +29,13 @@ export function MentionText(props: MentionTextProps) {
<RichText
inline
key={idx}
remoteContentPolicy={props.remoteContentPolicy}
>
{c.text}
</RichText>
);
} else if ("mention" in c) {
return (
<Mention key={idx} contacts={contacts || {}} contact={contact || {}} group={group} ship={c.mention} hideNicknames={hideNicknames} hideAvatars={hideAvatars} />
<Mention key={idx} contacts={contacts || {}} contact={contact || {}} group={group} ship={c.mention} />
);
}
return null;
@ -54,15 +49,14 @@ export function Mention(props: {
contact: Contact;
contacts?: Contacts;
group: Group;
hideNicknames: boolean;
hideAvatars: boolean;
}) {
const { contacts, ship, hideNicknames, hideAvatars } = props;
const { contacts, ship } = props;
let { contact } = props;
contact = (contact?.nickname) ? contact : contacts?.[ship];
const showNickname = (Boolean(contact?.nickname) && !hideNicknames);
const showNickname = useShowNickname(contact);
const name = showNickname ? contact?.nickname : cite(ship);
const [showOverlay, setShowOverlay] = useState(false);
const onDismiss = useCallback(() => {
@ -89,8 +83,6 @@ export function Mention(props: {
color={`#${uxToHex(contact?.color ?? '0x0')}`}
group={group}
onDismiss={onDismiss}
hideAvatars={hideAvatars || false}
hideNicknames={hideNicknames}
history={history}
/>
)}

View File

@ -3,12 +3,10 @@ import React, { PureComponent } from 'react';
import { Sigil } from '~/logic/lib/sigil';
import { Contact, Group } from '~/types';
import {
ProfileOverlay,
OVERLAY_HEIGHT
} from './ProfileOverlay';
import ProfileOverlay, { OVERLAY_HEIGHT } from './ProfileOverlay';
import { Box, BaseImage, ColProps } from '@tlon/indigo-react';
import { withLocalState } from '~/logic/state/local';
type OverlaySigilProps = ColProps & {
ship: string;
@ -16,12 +14,11 @@ type OverlaySigilProps = ColProps & {
color: string;
sigilClass: string;
group?: Group;
hideAvatars: boolean;
hideNicknames: boolean;
scrollWindow?: HTMLElement;
history: any;
api: any;
className: string;
hideAvatars: boolean;
}
interface OverlaySigilState {
@ -30,7 +27,7 @@ interface OverlaySigilState {
bottomSpace: number | 'auto';
}
export default class OverlaySigil extends PureComponent<OverlaySigilProps, OverlaySigilState> {
class OverlaySigil extends PureComponent<OverlaySigilProps, OverlaySigilState> {
public containerRef: React.Ref<HTMLDivElement>;
constructor(props) {
@ -89,11 +86,10 @@ export default class OverlaySigil extends PureComponent<OverlaySigilProps, Overl
contact,
color,
group,
hideAvatars,
hideNicknames,
history,
api,
sigilClass,
hideAvatars,
...rest
} = this.props;
@ -127,8 +123,6 @@ export default class OverlaySigil extends PureComponent<OverlaySigilProps, Overl
bottomSpace={state.bottomSpace}
group={group}
onDismiss={this.profileHide}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
history={history}
api={api}
{...rest}
@ -139,3 +133,5 @@ export default class OverlaySigil extends PureComponent<OverlaySigilProps, Overl
);
}
}
export default withLocalState(OverlaySigil, ['hideAvatars']);

View File

@ -1,10 +1,11 @@
import React, { PureComponent } from 'react';
import { Contact, Group } from '~/types';
import { cite } from '~/logic/lib/util';
import { cite, useShowNickname } from '~/logic/lib/util';
import { Sigil } from '~/logic/lib/sigil';
import { Box, Col, Button, Text, BaseImage, ColProps } from '@tlon/indigo-react';
import { withLocalState } from '~/logic/state/local';
export const OVERLAY_HEIGHT = 250;
@ -17,12 +18,11 @@ type ProfileOverlayProps = ColProps & {
group?: Group;
onDismiss(): void;
hideAvatars: boolean;
hideNicknames: boolean;
history: any;
api: any;
}
export class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
public popoverRef: React.Ref<typeof Col>;
constructor(props) {
@ -60,7 +60,6 @@ export class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
topSpace,
bottomSpace,
group = false,
hideNicknames,
hideAvatars,
history,
onDismiss,
@ -90,7 +89,7 @@ export class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
classes="brt2"
svgClass="brt2"
/>;
const showNickname = contact?.nickname && !hideNicknames;
const showNickname = useShowNickname(contact);
// TODO: we need to rethink this "top-level profile view" of other ships
/* if (!group.hidden) {
@ -147,3 +146,5 @@ export class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
);
}
}
export default withLocalState(ProfileOverlay, ['hideAvatars']);

View File

@ -1,16 +1,16 @@
import React, { PureComponent, Fragment } from 'react';
import { LocalUpdateRemoteContentPolicy } from "~/types/local-update";
import { BaseAnchor, BaseImage, Box, Button, Text } from '@tlon/indigo-react';
import { hasProvider } from 'oembed-parser';
import EmbedContainer from 'react-oembed-container';
import { memoize } from 'lodash';
import { withLocalState } from '~/logic/state/local';
import { RemoteContentPolicy } from '~/types/local-update';
interface RemoteContentProps {
url: string;
text?: string;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
unfold?: boolean;
renderUrl?: boolean;
remoteContentPolicy: RemoteContentPolicy;
imageProps?: any;
audioProps?: any;
videoProps?: any;
@ -29,7 +29,7 @@ const IMAGE_REGEX = new RegExp(/(jpg|img|png|gif|tiff|jpeg|webp|webm|svg)$/i);
const AUDIO_REGEX = new RegExp(/(mp3|wav|ogg)$/i);
const VIDEO_REGEX = new RegExp(/(mov|mp4|ogv)$/i);
export default class RemoteContent extends PureComponent<RemoteContentProps, RemoteContentState> {
class RemoteContent extends PureComponent<RemoteContentProps, RemoteContentState> {
private fetchController: AbortController | undefined;
constructor(props) {
super(props);
@ -200,3 +200,5 @@ export default class RemoteContent extends PureComponent<RemoteContentProps, Rem
}
}
}
export default withLocalState(RemoteContent, ['remoteContentPolicy']);

View File

@ -19,13 +19,21 @@ const DISABLED_BLOCK_TOKENS = [
const DISABLED_INLINE_TOKENS = [];
const RichText = React.memo(({ remoteContentPolicy, ...props }) => (
const RichText = React.memo(({ disableRemoteContent, ...props }) => (
<ReactMarkdown
{...props}
renderers={{
link: (props) => {
if (disableRemoteContent) {
props.remoteContentPolicy = {
imageShown: false,
audioShown: false,
videoShown: false,
oembedShown: false
};
}
if (hasProvider(props.href)) {
return <RemoteContent className="mw-100" url={props.href} remoteContentPolicy={remoteContentPolicy} />;
return <RemoteContent className="mw-100" url={props.href} />;
}
return <BaseAnchor target='_blank' rel='noreferrer noopener' borderBottom='1px solid' {...props}>{props.children}</BaseAnchor>;
},

View File

@ -4,11 +4,12 @@ import { Row, Box, Text, Icon, Button } from '@tlon/indigo-react';
import ReconnectButton from './ReconnectButton';
import { StatusBarItem } from './StatusBarItem';
import { Sigil } from '~/logic/lib/sigil';
import useLocalState from '~/logic/state/local';
const StatusBar = (props) => {
const invites = [].concat(...Object.values(props.invites).map(obj => Object.values(obj)));
const metaKey = (window.navigator.platform.includes('Mac')) ? '⌘' : 'Ctrl+';
const toggleOmnibox = useLocalState(state => state.toggleOmnibox);
return (
<Box
display='grid'
@ -24,7 +25,7 @@ const StatusBar = (props) => {
<Icon icon='Spaces' color='black'/>
</Button>
<StatusBarItem mr={2} onClick={() => props.api.local.setOmnibox()}>
<StatusBarItem mr={2} onClick={() => toggleOmnibox()}>
{ !props.doNotDisturb && (props.notificationsCount > 0 || invites.length > 0) &&
(<Box display="block" right="-8px" top="-8px" position="absolute" >
<Icon color="blue" icon="Bullet" />

View File

@ -5,6 +5,7 @@ import index from '~/logic/lib/omnibox';
import Mousetrap from 'mousetrap';
import OmniboxInput from './OmniboxInput';
import OmniboxResult from './OmniboxResult';
import { withLocalState } from '~/logic/state/local';
import defaultApps from '~/logic/lib/default-apps';
@ -39,7 +40,7 @@ export class Omnibox extends Component {
}
if (prevProps && this.props.show && prevProps.show !== this.props.show) {
Mousetrap.bind('escape', () => this.props.api.local.setOmnibox());
Mousetrap.bind('escape', this.props.toggle);
document.addEventListener('mousedown', this.handleClickOutside);
const touchstart = new Event('touchstart');
this.omniInput.input.dispatchEvent(touchstart);
@ -63,7 +64,7 @@ export class Omnibox extends Component {
if (this.state.query.length > 0) {
this.setState({ query: '', results: this.initialResults(), selected: [] });
} else if (this.props.show) {
this.props.api.local.setOmnibox();
this.props.toggleOmnibox();
}
};
@ -96,7 +97,7 @@ export class Omnibox extends Component {
handleClickOutside(evt) {
if (this.props.show && !this.omniBox.contains(evt.target)) {
this.setState({ results: this.initialResults(), query: '', selected: [] }, () => {
this.props.api.local.setOmnibox();
this.props.toggleOmnibox();
});
}
}
@ -116,7 +117,7 @@ export class Omnibox extends Component {
navigate(app, link) {
const { props } = this;
this.setState({ results: this.initialResults(), query: '' }, () => {
props.api.local.setOmnibox();
props.toggleOmnibox();
if (defaultApps.includes(app.toLowerCase())
|| app === 'profile'
|| app === 'Links'
@ -299,4 +300,4 @@ export class Omnibox extends Component {
}
}
export default withRouter(Omnibox);
export default withRouter(withLocalState(Omnibox, ['toggleOmnibox', 'omniboxShown']));

View File

@ -19,6 +19,7 @@ import { ColorInput } from "~/views/components/ColorInput";
import GlobalApi from "~/logic/api/global";
import { ImageInput } from "~/views/components/ImageInput";
import { S3State } from "~/types";
import useLocalState from "~/logic/state/local";
interface ContactCardProps {
contact: Contact;
@ -26,8 +27,6 @@ interface ContactCardProps {
api: GlobalApi;
s3: S3State;
rootIdentity: Contact;
hideAvatars: boolean;
hideNicknames: boolean;
}
const formSchema = Yup.object({
@ -72,6 +71,9 @@ const emptyContact = {
};
export function ContactCard(props: ContactCardProps) {
const { hideAvatars, hideNicknames } = useLocalState(({ hideAvatars, hideNicknames }) => ({
hideAvatars, hideNicknames
}));
const us = `~${window.ship}`;
const { contact, rootIdentity } = props;
const onSubmit = async (values: any, actions: FormikHelpers<Contact>) => {
@ -114,11 +116,11 @@ export function ContactCard(props: ContactCardProps) {
};
const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : "#000000";
const image = (!props?.hideAvatars && contact?.avatar)
const image = (!hideAvatars && contact?.avatar)
? <BaseImage src={contact.avatar} width='100%' height='100%' style={{ objectFit: 'cover' }} />
: <Sigil ship={us} size={32} color={hexColor} />;
const nickname = (!props.hideNicknames && contact?.nickname) ? contact.nickname : "";
const nickname = (!hideNicknames && contact?.nickname) ? contact.nickname : "";
return (
<Box p={4} height="100%" overflowY="auto">

View File

@ -68,8 +68,6 @@ export function GroupsPane(props: GroupsPaneProps) {
group={group!}
api={api}
s3={props.s3}
hideAvatars={props.hideAvatars}
hideNicknames={props.hideNicknames}
notificationsGroupConfig={props.notificationsGroupConfig}
{...routeProps}

View File

@ -31,6 +31,7 @@ import { Dropdown } from '~/views/components/Dropdown';
import GlobalApi from '~/logic/api/global';
import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction';
import styled from 'styled-components';
import useLocalState from '~/logic/state/local';
const TruncText = styled(Box)`
white-space: nowrap;
@ -104,10 +105,8 @@ export function Participants(props: {
group: Group;
association: Association;
api: GlobalApi;
hideAvatars: boolean;
hideNicknames: boolean;
}) {
const { api, hideAvatars, hideNicknames } = props;
const { api } = props;
const tabFilters: Record<
ParticipantsTabId,
(p: Participant) => boolean
@ -232,8 +231,6 @@ export function Participants(props: {
group={props.group}
contact={c}
association={props.association}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
/>
))
) : (
@ -254,11 +251,12 @@ function Participant(props: {
group: Group;
role?: RoleTags;
api: GlobalApi;
hideAvatars: boolean;
hideNicknames: boolean;
}) {
const { contact, association, group, api } = props;
const { title } = association.metadata;
const { hideAvatars, hideNicknames } = useLocalState(
({ hideAvatars, hideNicknames }) => ({ hideAvatars, hideNicknames })
);
const color = uxToHex(contact.color);
const isInvite = 'invite' in group.policy;
@ -296,13 +294,13 @@ function Participant(props: {
}, [api, association]);
const avatar =
contact?.avatar !== null && !props.hideAvatars ? (
contact?.avatar !== null && !hideAvatars ? (
<img src={contact.avatar} height={32} width={32} className="dib" />
) : (
<Sigil ship={contact.patp} size={32} color={`#${color}`} />
);
const hasNickname = contact.nickname && !props.hideNicknames;
const hasNickname = contact.nickname && !hideNicknames;
return (
<>

View File

@ -38,8 +38,6 @@ export function PopoverRoutes(
association: Association;
s3: S3State;
api: GlobalApi;
hideAvatars: boolean;
hideNicknames: boolean;
notificationsGroupConfig: GroupNotificationsConfig;
rootIdentity: Contact;
} & RouteComponentProps
@ -135,8 +133,6 @@ export function PopoverRoutes(
contacts={props.contacts}
association={props.association}
api={props.api}
hideAvatars={props.hideAvatars}
hideNicknames={props.hideNicknames}
/>
)}
{view === "profile" && (
@ -144,8 +140,6 @@ export function PopoverRoutes(
contact={props.contacts[window.ship]}
rootIdentity={props.rootIdentity}
api={props.api}
hideAvatars={props.hideAvatars}
hideNicknames={props.hideNicknames}
path={props.association["group-path"]}
s3={props.s3}
/>

View File

@ -38,12 +38,6 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) {
const workspace =
baseUrl === "/~landscape/home" ? "/home" : association["group-path"];
const title = props.title || association?.metadata?.title;
const disableRemoteContent = {
audioShown: false,
imageShown: false,
oembedShown: false,
videoShown: false,
};
return (
<Col width="100%" height="100%" overflowY="hidden">
<Box
@ -91,9 +85,9 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) {
>
<RichText
color="gray"
remoteContentPolicy={disableRemoteContent}
mb="0"
display="inline-block"
disableRemoteContent
>
{association?.metadata?.description}
</RichText>