Merge pull request #3639 from urbit/mp/interface/more-indigoifying

interface: indigo-reacting components, removing popout logic
This commit is contained in:
Liam Fitzgerald 2020-10-05 13:03:59 +10:00 committed by GitHub
commit 87b97beaf8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 243 additions and 317 deletions

View File

@ -81,10 +81,10 @@ export function uxToHex(ux) {
}
export function hexToUx(hex) {
const ux = _.chain(hex.split(""))
const ux = _.chain(hex.split(''))
.chunk(4)
.map((x) => _.dropWhile(x, (y) => y === 0).join(""))
.join(".");
.map(x => _.dropWhile(x, y => y === 0).join(''))
.join('.');
return `0x${ux}`;
}
@ -236,28 +236,28 @@ export function stringToTa(string) {
export function makeRoutePath(
resource,
popout = false,
page = 0,
url = null,
index = 0,
compage = 0
) {
let route = "/~link" + (popout ? "/popout" : "") + resource;
let route = '/~link' + resource;
if (!url) {
if (page !== 0) {
route = route + "/" + page;
route = route + '/' + page;
}
} else {
route = `${route}/${page}/${index}/${base64urlEncode(url)}`;
if (compage !== 0) {
route = route + "/" + compage;
route = route + '/' + compage;
}
}
return route;
}
export function amOwnerOfGroup(groupPath) {
if (!groupPath) return false;
if (!groupPath)
return false;
const groupOwner = /(\/~)?\/~([a-z-]{3,})\/.*/.exec(groupPath)[2];
return window.ship === groupOwner;
}
@ -265,20 +265,20 @@ export function amOwnerOfGroup(groupPath) {
export function getContactDetails(contact) {
const member = !contact;
contact = contact || {
nickname: "",
nickname: '',
avatar: null,
color: "0x0",
color: '0x0'
};
const nickname = contact.nickname || "";
const color = uxToHex(contact.color || "0x0");
const nickname = contact.nickname || '';
const color = uxToHex(contact.color || '0x0');
const avatar = contact.avatar || null;
return { nickname, color, member, avatar };
}
export function stringToSymbol(str) {
let result = '';
for (var i = 0; i < str.length; i++) {
var n = str.charCodeAt(i);
for (let i = 0; i < str.length; i++) {
const n = str.charCodeAt(i);
if (((n >= 97) && (n <= 122)) ||
((n >= 48) && (n <= 57))) {
result += str[i];
@ -298,12 +298,12 @@ export function stringToSymbol(str) {
export function scrollIsAtTop(container) {
if (
(navigator.userAgent.includes("Safari") &&
navigator.userAgent.includes("Chrome")) ||
navigator.userAgent.includes("Firefox")
(navigator.userAgent.includes('Safari') &&
navigator.userAgent.includes('Chrome')) ||
navigator.userAgent.includes('Firefox')
) {
return container.scrollTop === 0;
} else if (navigator.userAgent.includes("Safari")) {
} else if (navigator.userAgent.includes('Safari')) {
return (
container.scrollHeight + Math.round(container.scrollTop) <=
container.clientHeight + 10
@ -315,15 +315,15 @@ export function scrollIsAtTop(container) {
export function scrollIsAtBottom(container) {
if (
(navigator.userAgent.includes("Safari") &&
navigator.userAgent.includes("Chrome")) ||
navigator.userAgent.includes("Firefox")
(navigator.userAgent.includes('Safari') &&
navigator.userAgent.includes('Chrome')) ||
navigator.userAgent.includes('Firefox')
) {
return (
container.scrollHeight - Math.round(container.scrollTop) <=
container.clientHeight + 10
);
} else if (navigator.userAgent.includes("Safari")) {
} else if (navigator.userAgent.includes('Safari')) {
return container.scrollTop === 0;
} else {
return false;

View File

@ -220,9 +220,9 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
/>
<Route
exact
path="/~chat/(popout)?/room/(~)?/:ship/:station+"
path="/~chat/room/(~)?/:ship/:station+"
render={(props) => {
let station = `/${props.match.params.ship}/${props.match.params.station}`;
const station = `/${props.match.params.ship}/${props.match.params.station}`;
const mailbox = inbox[station] || {
config: {
read: 0,
@ -247,14 +247,11 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
const group = groups[association['group-path']] || groupBunts.group();
const popout = props.match.url.includes('/popout/');
return (
<Skeleton
associations={associations}
invites={invites}
sidebarHideOnMobile={true}
popout={popout}
sidebarShown={sidebarShown}
sidebar={renderChannelSidebar(props, station)}
>
@ -271,7 +268,6 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
group={group}
pendingMessages={pendingMessages}
s3={s3}
popout={popout}
sidebarShown={sidebarShown}
chatInitialized={chatInitialized}
hideAvatars={hideAvatars}
@ -285,10 +281,9 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
/>
<Route
exact
path="/~chat/(popout)?/settings/(~)?/:ship/:station+"
path="/~chat/settings/(~)?/:ship/:station+"
render={(props) => {
let station = `/${props.match.params.ship}/${props.match.params.station}`;
const popout = props.match.url.includes('/popout/');
const association =
station in associations['chat'] ? associations.chat[station] : {};
@ -299,7 +294,6 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
associations={associations}
invites={invites}
sidebarHideOnMobile={true}
popout={popout}
sidebarShown={sidebarShown}
sidebar={renderChannelSidebar(props, station)}
>
@ -313,7 +307,6 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
associations={associations.contacts}
api={api}
inbox={inbox}
popout={popout}
sidebarShown={sidebarShown}
/>
</Skeleton>

View File

@ -33,7 +33,6 @@ type ChatScreenProps = RouteComponentProps<{
group: Group;
pendingMessages: Map<Path, Envelope[]>;
s3: any;
popout: boolean;
sidebarShown: boolean;
chatInitialized: boolean;
envelopes: Envelope[];
@ -131,7 +130,7 @@ export class ChatScreen extends Component<ChatScreenProps, ChatScreenState> {
const unreadCount = props.mailboxSize - props.read;
const unreadMsg = unreadCount > 0 && props.envelopes[unreadCount - 1];
return (
<div
key={props.station}

View File

@ -6,7 +6,6 @@ import { SidebarSwitcher } from '~/views/components/SidebarSwitch';
import { deSig } from '~/logic/lib/util';
const ChatHeader = (props) => {
const isInPopout = props.popout ? 'popout/' : '';
const group = Array.from(props.group.members);
let title = props.station.substr(1);
if (props.association &&
@ -32,11 +31,10 @@ const ChatHeader = (props) => {
>
<SidebarSwitcher
sidebarShown={props.sidebarShown}
popout={props.popout}
api={props.api}
/>
<Link
to={'/~chat/' + isInPopout + 'room' + props.station}
to={'/~chat/' + 'room' + props.station}
className="pt2 white-d"
>
<h2
@ -51,9 +49,7 @@ const ChatHeader = (props) => {
</Link>
<TabBar
location={props.location}
popoutHref={`/~chat/popout/room${props.station}`}
settings={`/~chat/${isInPopout}settings${props.station}`}
popout={props.popout}
settings={`/~chat/settings${props.station}`}
/>
</div>
</Fragment>

View File

@ -85,7 +85,7 @@ export default class ChatMessage extends Component<ChatMessageProps> {
isLastMessage,
unreadMarkerRef
} = this.props;
const renderSigil = Boolean((nextMsg && msg.author !== nextMsg.author) || !nextMsg || msg.number === 1);
const dayBreak = nextMsg && new Date(msg.when).getDate() !== new Date(nextMsg.when).getDate();
@ -221,7 +221,7 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
export const MessageWithoutSigil = ({ timestamp, msg, remoteContentPolicy, measure }) => (
<>
<p className="child ph1 mono f9 gray2 dib">{timestamp}</p>
<p className="child pr1 mono f9 gray2 dib">{timestamp}</p>
<div className="fr clamp-message white-d pr3 lh-copy" style={{ flexGrow: 1 }}>
<MessageContent content={msg.letter} remoteContentPolicy={remoteContentPolicy} measure={measure}/>
</div>

View File

@ -1,11 +1,7 @@
import React, { memo } from 'react';
import { Button, Text, Box } from '@tlon/indigo-react';
export const DeleteButton = memo(({ isOwner, station, changeLoading, association, contacts, api, history }) => {
const leaveButtonClasses = (!isOwner) ? 'pointer' : 'c-default';
const deleteButtonClasses = (isOwner) ?
'b--red2 red2 pointer bg-gray0-d' :
'b--gray3 gray3 bg-gray0-d c-default';
const deleteChat = () => {
changeLoading(
true,
@ -13,7 +9,7 @@ export const DeleteButton = memo(({ isOwner, station, changeLoading, association
isOwner ? 'Deleting chat...' : 'Leaving chat...',
() => {
api.chat.delete(station).then(() => {
history.push("/~chat");
history.push('/~chat');
});
}
);
@ -23,34 +19,30 @@ export const DeleteButton = memo(({ isOwner, station, changeLoading, association
const unmanagedVillage = !contacts[groupPath];
return (
<div className="w-100 cf">
<div className={'w-100 fl mt3 ' + ((isOwner) ? 'o-30' : '')}>
<p className="f8 mt3 lh-copy db">Leave Chat</p>
<p className="f9 gray2 db mb4">
<Box width='100%'>
<Box width='100%' mt='3' opacity={(isOwner) ? '0.3' : '1'}>
<Text fontSize='1' mt='3' display='block' mb='1'>Leave Chat</Text>
<Text fontSize='0' gray display='block' mb='4'>
Remove this chat from your chat list.{' '}
{unmanagedVillage
{unmanagedVillage
? 'You will need to request for access again'
: 'You will need to join again from the group page.'
: 'You will need to join again from the group page'
}
</p>
<a onClick={(!isOwner) ? deleteChat : null}
className={
'dib f9 black gray4-d bg-gray0-d ba pa2 b--black b--gray1-d ' +
leaveButtonClasses
}>
</Text>
<Button onClick={(!isOwner) ? deleteChat : null}>
Leave this chat
</a>
</div>
<div className={'w-100 fl mt3 ' + ((!isOwner) ? 'o-30' : '')}>
<p className="f8 mt3 lh-copy db">Delete Chat</p>
<p className="f9 gray2 db mb4">
</Button>
</Box>
<Box width='100%' mt='3' opacity={(isOwner) ? '0.3' : '1'}>
<Text display='block' fontSize='1' mt='3' mb='1'>Delete Chat</Text>
<Text display='block' gray fontSize='0' mb='4'>
Permanently delete this chat.{' '}
All current members will no longer see this chat.
</p>
<a onClick={(isOwner) ? deleteChat : null}
className={'dib f9 ba pa2 ' + deleteButtonClasses}
>Delete this chat</a>
</div>
</div>
</Text>
<Button destructive onClick={(isOwner) ? deleteChat : null}>Delete this chat</Button>
</Box>
</Box>
);
})
});
DeleteButton.displayName = 'DeleteButton';

View File

@ -2,9 +2,9 @@ import React, { Component } from 'react';
import Toggle from '~/views/components/toggle';
import { InviteSearch } from '~/views/components/InviteSearch';
import { Button, Text, Box } from '@tlon/indigo-react';
export class GroupifyButton extends Component {
constructor(props) {
super(props);
@ -26,23 +26,23 @@ export class GroupifyButton extends Component {
this.setState({ inclusive: Boolean(event.target.checked) });
}
renderInclusiveToggle() {
renderInclusiveToggle(inclusive) {
return this.state.targetGroup ? (
<div className="mt4">
<Box mt='4'>
<Toggle
boolean={inclusive}
change={this.changeInclusive.bind(this)}
/>
<span className="dib f9 white-d inter ml3">
<Text display='inline-block' fontSize='0' ml='3'>
Add all members to group
</span>
<p className="f9 gray2 pt1" style={{ paddingLeft: 40 }}>
</Text>
<Text display='block' fontSize='0' gray pt='1' pl='40px'>
Add chat members to the group if they aren't in it yet
</p>
</div>
) : <div />;
</Text>
</Box>
) : <Box />;
}
render() {
const { inclusive, targetGroup } = this.state;
const {
@ -65,12 +65,12 @@ export class GroupifyButton extends Component {
}
return (
<div className={'w-100 fl mt3'} style={{ maxWidth: '29rem' }}>
<p className="f8 mt3 lh-copy db">Convert Chat</p>
<p className="f9 gray2 db mb4">
<Box width='100%' mt='3' maxWidth='29rem'>
<Text display='block' fontSize='1' mt='3' mb='1'>Convert Chat</Text>
<Text gray display='block' mb='4' fontSize='0'>
Convert this chat into a group with associated chat, or select a
group to add this chat to.
</p>
group to add this chat to
</Text>
<InviteSearch
groups={groups}
contacts={contacts}
@ -83,8 +83,8 @@ export class GroupifyButton extends Component {
}}
setInvite={this.changeTargetGroup.bind(this)}
/>
{this.renderInclusiveToggle()}
<a onClick={() => {
{this.renderInclusiveToggle(inclusive)}
<Button mt='3' onClick={() => {
changeLoading(true, true, 'Converting to group...', () => {
api.chat.groupify(
station, targetGroup, inclusive
@ -93,11 +93,8 @@ export class GroupifyButton extends Component {
});
});
}}
className={
'dib f9 black gray4-d bg-gray0-d ba pa2 mt4 b--black ' +
'b--gray1-d pointer'
}>Convert to group</a>
</div>
>Convert to group</Button>
</Box>
);
}
}

View File

@ -8,6 +8,8 @@ import ChatHeader from './lib/ChatHeader';
import { DeleteButton } from './lib/delete-button';
import { GroupifyButton } from './lib/groupify-button';
import { Text, Col, Box } from '@tlon/indigo-react';
export class SettingsScreen extends Component {
constructor(props) {
super(props);
@ -59,7 +61,6 @@ export class SettingsScreen extends Component {
}
renderNormal() {
const { state } = this;
const {
associations,
association,
@ -74,7 +75,7 @@ export class SettingsScreen extends Component {
return (
<Fragment>
<h2 className="f8 pb2">Chat Settings</h2>
<Text display='block' pb='2' fontSize='1'>Chat Settings</Text>
<GroupifyButton
isOwner={isOwner}
association={association}
@ -116,21 +117,13 @@ export class SettingsScreen extends Component {
group,
association,
station,
popout,
sidebarShown,
match,
location
} = this.props;
const isInPopout = popout ? "popout/" : "";
const title =
( association &&
('metadata' in association) &&
(association.metadata.title !== '')
) ? association.metadata.title : station.substr(1);
return (
<div className="h-100 w-100 overflow-x-hidden flex flex-column white-d">
<Col height='100%' width='100%' overflowX='hidden'>
<ChatHeader
match={match}
location={location}
@ -139,11 +132,11 @@ export class SettingsScreen extends Component {
association={association}
station={station}
sidebarShown={sidebarShown}
popout={popout} />
<div className="w-100 pl3 mt4 cf">
/>
<Box width='100%' pl='3' mt='3'>
{(state.isLoading) ? this.renderLoading() : this.renderNormal() }
</div>
</div>
</Box>
</Col>
);
}
}

View File

@ -6,7 +6,7 @@ import ErrorBoundary from '~/views/components/ErrorBoundary';
export class Skeleton extends Component {
render() {
// sidebar and chat panel conditional classes
const sidebarHide = (!this.props.sidebarShown || this.props.popout)
const sidebarHide = (!this.props.sidebarShown)
? 'dn' : '';
const sidebarHideOnMobile = this.props.sidebarHideOnMobile
@ -22,18 +22,11 @@ export class Skeleton extends Component {
'w-100 inter pt4 f8': !this.props.chatHideOnMobile
});
// popout switches out window chrome and borders
const popoutWindow = this.props.popout
? '' : 'ph4-m ph4-l ph4-xl pb4-m pb4-l pb4-xl';
const popoutBorder = this.props.popout
? '' : 'ba-m ba-l ba-xl b--gray4 b--gray1-d br1 ';
return (
// app outer skeleton
<div className={'h-100 w-100 ' + popoutWindow}>
<div className='h-100 w-100 ph4-m ph4-l ph4-xl pb4-m pb4-l pb4-xl'>
{/* app window borders */}
<div className={ 'bg-white bg-gray0-d cf w-100 flex h-100 ' + popoutBorder }>
<div className='bg-white bg-gray0-d cf w-100 flex h-100 ba-m ba-l ba-xl b--gray4 b--gray1-d br1'>
{/* sidebar skeleton, hidden on mobile when in chat panel */}
<div
className={

View File

@ -1,9 +1,7 @@
import React, { Component } from 'react';
import { Route, Link } from 'react-router-dom';
import classnames from 'classnames';
import { Route } from 'react-router-dom';
import Helmet from 'react-helmet';
import { Popout } from './components/lib/icons/popout';
import { History } from './components/history';
import { Input } from './components/input';
@ -54,16 +52,8 @@ export default class DojoApp extends Component {
>
<Route
exact
path="/~dojo/:popout?"
path="/~dojo/"
render={(props) => {
const popout = Boolean(props.match.params.popout);
const popoutClasses = classnames({
'mh4-m mh4-l mh4-xl': !popout,
'mb4-m mb4-l mb4-xl': !popout,
'ba-m ba-l ba-xl': !popout
});
return (
<div className="w-100 h-100 flex-m flex-l flex-xl">
<div
@ -75,14 +65,13 @@ export default class DojoApp extends Component {
className={
'pa3 bg-white bg-gray0-d black white-d mono w-100 f8 relative' +
' h-100-m40-s b--gray2 br1 flex-auto flex flex-column ' +
popoutClasses
'mh4-m mh4-l mh4-xl mb4-m mb4-l mb4-xl ba-m ba-l ba-xl'
}
style={{
lineHeight: '1.4',
cursor: 'text'
}}
>
<Popout popout={popout} />
<History commandLog={this.state.txt} />
<Input
ship={this.props.ship}

View File

@ -1,29 +0,0 @@
import React, { Component } from 'react';
export class Popout extends Component {
render() {
const hidePopoutIcon = this.props.popout
? 'dn-m dn-l dn-xl'
: 'dib-m dib-l dib-xl';
return (
<div
className="db tr z-2"
style={{
right: 16,
top: 16
}}
>
<a href="/~dojo/popout" target="_blank">
<img
className={'flex-shrink-0 dn ' + hidePopoutIcon}
src="/~dojo/img/popout.png"
height="16"
width="16"
/>
</a>
</div>
);
}
}
export default Popout;

View File

@ -24,7 +24,7 @@ export function InvitePopover(props: InvitePopoverProps) {
const { baseUrl, api, association } = props;
const relativePath = (p: string) => baseUrl + p;
const { title } = association.metadata;
const { title } = association?.metadata || '';
const innerRef = useRef(null);
const history = useHistory();

View File

@ -49,7 +49,6 @@ export function LinkResource(props: LinkResourceProps) {
? associations.graph[appPath]
: { metadata: {} };
const contactDetails = contacts[resource["group-path"]] || {};
const popout = props.match.url.includes("/popout/");
const graph = graphs[resourcePath] || null;
useEffect(() => {
@ -131,7 +130,6 @@ export function LinkResource(props: LinkResourceProps) {
comments={node.children}
resourcePath={resourcePath}
contacts={contactDetails}
popout={false}
api={api}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}

View File

@ -91,11 +91,10 @@ export default class LinksApp extends Component {
</Skeleton>
)}
/>
<Route exact path="/~link/(popout)?/:ship/:name/settings"
<Route exact path="/~link/:ship/:name/settings"
render={ (props) => {
const resourcePath =
const resourcePath =
`${props.match.params.ship}/${props.match.params.name}`;
const popout = props.match.url.includes('/popout/');
const metPath = `/ship/~${resourcePath}`;
const resource =
associations.graph[metPath] ?
@ -113,7 +112,6 @@ export default class LinksApp extends Component {
groups={groups}
selected={resourcePath}
sidebarShown={sidebarShown}
popout={popout}
graphKeys={graphKeys}
api={api}>
<SettingsScreen
@ -126,16 +124,15 @@ export default class LinksApp extends Component {
group={group}
amOwner={amOwner}
resourcePath={resourcePath}
popout={popout}
api={api}
{...props} />
</Skeleton>
);
}}
/>
<Route exact path="/~link/(popout)?/:ship/:name"
<Route exact path="/~link/:ship/:name"
render={ (props) => {
const resourcePath =
const resourcePath =
`${props.match.params.ship}/${props.match.params.name}`;
const metPath = `/ship/~${resourcePath}`;
const resource =
@ -143,7 +140,6 @@ export default class LinksApp extends Component {
associations.graph[metPath] : { metadata: {} };
const contactDetails = contacts[resource['group-path']] || {};
const popout = props.match.url.includes('/popout/');
const graph = graphs[resourcePath] || null;
return (
@ -154,7 +150,6 @@ export default class LinksApp extends Component {
selected={resourcePath}
sidebarShown={sidebarShown}
sidebarHideMobile={true}
popout={popout}
api={api}
graphKeys={graphKeys}>
<LinkList
@ -164,7 +159,6 @@ export default class LinksApp extends Component {
graph={graph}
graphResource={graphKeys.has(resourcePath)}
resourcePath={resourcePath}
popout={popout}
metadata={resource.metadata}
contacts={contactDetails}
hideAvatars={hideAvatars}
@ -177,15 +171,14 @@ export default class LinksApp extends Component {
);
}}
/>
<Route exact path="/~link/(popout)?/:ship/:name/:index"
<Route exact path="/~link/:ship/:name/:index"
render={ (props) => {
const resourcePath =
const resourcePath =
`${props.match.params.ship}/${props.match.params.name}`;
const metPath = `/ship/~${resourcePath}`;
const resource =
associations.graph[metPath] ?
associations.graph[metPath] : { metadata: {} };
const popout = props.match.url.includes('/popout/');
const contactDetails = contacts[resource['group-path']] || {};
@ -197,7 +190,7 @@ export default class LinksApp extends Component {
}
const index = parseInt(indexArr[1], 10);
const node = !!graph ? graph.get(index) : null;
const node = Boolean(graph) ? graph.get(index) : null;
return (
<Skeleton
@ -207,7 +200,6 @@ export default class LinksApp extends Component {
selected={resourcePath}
sidebarShown={sidebarShown}
sidebarHideMobile={true}
popout={popout}
graphKeys={graphKeys}
api={api}>
<LinkDetail
@ -218,7 +210,6 @@ export default class LinksApp extends Component {
name={props.match.params.name}
resource={resource}
contacts={contactDetails}
popout={popout}
sidebarShown={sidebarShown}
api={api}
hideAvatars={hideAvatars}

View File

@ -83,14 +83,13 @@ export const ChannelSidebar = (props) => {
}
const activeClasses = (props.active === 'collections') ? ' ' : 'dn-s ';
const hiddenClasses = !!props.popout ? false : props.sidebarShown;
return (
<div className={
`bn br-m br-l br-xl b--gray4 b--gray1-d lh-copy h-100` +
`flex-shrink-0 mw5-m mw5-l mw5-xl pt3 pt0-m pt0-l pt0-xl relative ` +
activeClasses +
((hiddenClasses) ? 'flex-basis-100-s flex-basis-30-ns' : 'dn')
((props.sidebarShown) ? 'flex-basis-100-s flex-basis-30-ns' : 'dn')
}>
<div className="overflow-y-scroll h-100">
<div className="w-100 bg-transparent">

View File

@ -20,12 +20,12 @@ export class Pagination extends Component {
return (
<div className="w-100 inter relative pv6">
<div className={prevDisplay + ' inter f8'}>
<Link to={makeRoutePath(props.resourcePath, props.popout, prevPage)}>
<Link to={makeRoutePath(props.resourcePath, prevPage)}>
&#60;- Previous Page
</Link>
</div>
<div className={nextDisplay + ' inter f8'}>
<Link to={makeRoutePath(props.resourcePath, props.popout, nextPage)}>
<Link to={makeRoutePath(props.resourcePath, nextPage)}>
Next Page -&gt;
</Link>
</div>

View File

@ -45,7 +45,6 @@ export const LinkDetail = (props) => {
style={{ height: 48 }}>
<SidebarSwitcher
sidebarShown={props.sidebarShown}
popout={props.popout}
api={props.api}
/>
<Link className="dib f9 fw4 pt2 gray2 lh-solid"
@ -58,8 +57,6 @@ export const LinkDetail = (props) => {
</Link>
<TabBar
location={props.location}
popout={props.popout}
popoutHref={`/~link/popout/${resourcePath}/${props.match.params.index}`}
settings={`/~link/${resourcePath}/settings`}
/>
</div>
@ -84,7 +81,6 @@ export const LinkDetail = (props) => {
comments={props.node.children}
resourcePath={resourcePath}
contacts={props.contacts}
popout={props.popout}
api={props.api}
hideAvatars={props.hideAvatars}
hideNicknames={props.hideNicknames}

View File

@ -1,4 +1,4 @@
import React, { Component, useEffect } from "react";
import React, { useEffect } from "react";
import { TabBar } from '~/views/components/chat-link-tabbar';
import { SidebarSwitcher } from '~/views/components/SidebarSwitch';
@ -44,7 +44,6 @@ export const LinkList = (props) => {
>
<SidebarSwitcher
sidebarShown={props.sidebarShown}
popout={props.popout}
api={props.api} />
<h2
className="dib f9 fw4 pt2 lh-solid v-top black white-d"
@ -53,8 +52,6 @@ export const LinkList = (props) => {
</h2>
<TabBar
location={props.location}
popout={props.popout}
popoutHref={`/~link/popout/${resource}`}
settings={`/~link/${resource}/settings`}
/>
</div>

View File

@ -9,6 +9,8 @@ import SidebarSwitcher from '~/views/components/SidebarSwitch';
import { MetadataSettings } from '~/views/components/metadata/settings';
import { Box, Text, Button, Col, Row } from '@tlon/indigo-react';
export class SettingsScreen extends Component {
constructor(props) {
super(props);
@ -77,16 +79,15 @@ export class SettingsScreen extends Component {
return null;
} else {
return (
<div className="w-100 fl mt3">
<p className="f8 mt3 lh-copy db">Remove Collection</p>
<p className="f9 gray2 db mb4">
Remove this collection from your collection list.
</p>
<a onClick={this.removeCollection.bind(this)}
className="dib f9 black gray4-d bg-gray0-d ba pa2 b--black b--gray1-d pointer">
<Box width='100%' mt='3'>
<Text display='block' mt='3' fontSize='1' mb='1'>Remove Collection</Text>
<Text display='block' fontSize='0' gray mb='4'>
Remove this collection from your collection list
</Text>
<Button onClick={this.removeCollection.bind(this)}>
Remove collection
</a>
</div>
</Button>
</Box>
);
}
}
@ -98,16 +99,15 @@ export class SettingsScreen extends Component {
return null;
} else {
return (
<div className="w-100 fl mt3">
<p className="f8 mt3 lh-copy db">Delete Collection</p>
<p className="f9 gray2 db mb4">
Delete this collection, for you and all group members.
</p>
<a onClick={this.deleteCollection.bind(this)}
className="dib f9 ba pa2 b--red2 red2 pointer bg-gray0-d mb4">
<Box width='100%' mt='3'>
<Text fontSize='1' mt='3' display='block' mb='1'>Delete collection</Text>
<Text fontSize='0' gray display='block' mb='4'>
Delete this collection, for you and all group members
</Text>
<Button primary onClick={this.deleteCollection.bind(this)} destructive mb='4'>
Delete collection
</a>
</div>
</Button>
</Box>
);
}
}
@ -123,43 +123,44 @@ export class SettingsScreen extends Component {
return <LoadingScreen />;
} else if (!props.graphResource) {
props.history.push('/~link');
return <div></div>;
return <Box />;
}
return (
<div className="h-100 w-100 overflow-x-hidden flex flex-column white-d">
<div className="w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8"
style={{ height: '1rem' }}>
<Col height='100%' width='100' overflowX='hidden'>
<Box width='100%' display={['block', 'none']} pt='4' pb='6' pl='3' fontSize='1' height='1rem'>
<Link to="/~link">{'⟵ All Collections'}</Link>
</div>
<div
className={
"pl4 pt2 bb b--gray4 b--gray1-d flex relative overflow-x-scroll " +
"overflow-x-auto-l overflow-x-auto-xl flex-shrink-0"
}
style={{ height: 48 }}>
</Box>
<Row
pl='12px'
pt='2'
borderBottom='1px solid'
borderColor='washedGray'
flexShrink='0'
overflowX={['scroll', 'auto']}
height='48px'
>
<SidebarSwitcher
sidebarShown={this.props.sidebarShown}
popout={this.props.popout}
api={this.props.api}
/>
<Link className="dib f9 fw4 pt2 gray2 lh-solid"
to={`/~link/${props.resourcePath}`}>
<h2
className="dib f9 fw4 lh-solid v-top"
style={{ width: 'max-content' }}>
<Text
display='inline-block'
fontSize='0'
verticalAlign='top'
width='max-content'>
{title}
</h2>
</Text>
</Link>
<TabBar
location={props.location}
popout={props.popout}
popoutHref={`/~link/popout/${props.resourcePath}/settings`}
settings={`/~link/${props.resourcePath}/settings`}
/>
</div>
<div className="w-100 pl3 mt4 cf">
<h2 className="f8 pb2">Collection Settings</h2>
</Row>
<Box width='100' pl='3' mt='3'>
<Text display='block' fontSize='1' pb='2'>Collection Settings</Text>
{this.renderRemove()}
{this.renderDelete()}
<MetadataSettings
@ -176,8 +177,8 @@ export class SettingsScreen extends Component {
classes="absolute right-1 bottom-1 pa2 ba b--black b--gray0-d white-d"
text={this.state.type}
/>
</div>
</div>
</Box>
</Col>
);
}
}

View File

@ -6,24 +6,16 @@ export class Skeleton extends Component {
render() {
const { props } = this;
const rightPanelHide = props.rightPanelHide ? 'dn-s' : '';
const popout = props.popout ? props.popout : false;
const popoutWindow = (popout)
? '' : 'ph4-m ph4-l ph4-xl pb4-m pb4-l pb4-xl';
const popoutBorder = (popout)
? '' : 'ba-m ba-l ba-xl b--gray4 b--gray1-d br1';
const linkInvites = ('/link' in props.invites)
? props.invites['/link'] : {};
return (
<div className={'absolute w-100 ' + popoutWindow}
<div className='absolute w-100 ph4-m ph4-l ph4-xl pb4-m pb4-l pb4-xl'
style={{ height: 'calc(100% - 45px)' }}>
<div className={'bg-white bg-gray0-d cf w-100 h-100 flex ' + popoutBorder}>
<div className='bg-white bg-gray0-d cf w-100 h-100 flex ba-m ba-l ba-xl b--gray4 b--gray1-d br1'>
<ChannelSidebar
active={props.active}
popout={popout}
associations={props.associations}
invites={linkInvites}
groups={props.groups}

View File

@ -84,7 +84,6 @@ export default function PublishApp(props: PublishAppProps) {
]}
>
<RouterSkeleton
popout={location.pathname.includes("popout/")}
active={active}
sidebarShown={sidebarShown}
invites={invites}

View File

@ -37,7 +37,6 @@ export function NotePreview(props: NotePreviewProps) {
comment = `${note["num-comments"]} Comments`;
}
const date = moment(note["date-created"]).fromNow();
//const popout = props.popout ? "popout/" : "";
const url = `${props.book}/note/${note["note-id"]}`;
return (

View File

@ -1,9 +1,11 @@
import React from "react";
import RemoteContent from "~/views/components/RemoteContent";
import React from 'react';
import RemoteContent from '~/views/components/RemoteContent';
import { hasProvider } from 'oembed-parser';
import ReactMarkdown from 'react-markdown';
import RemarkDisableTokenizers from 'remark-disable-tokenizers';
import { BaseAnchor, Text } from '@tlon/indigo-react';
const DISABLED_BLOCK_TOKENS = [
'indentedCode',
'atxHeading',
@ -17,24 +19,27 @@ const DISABLED_BLOCK_TOKENS = [
const DISABLED_INLINE_TOKENS = [];
const RichText = React.memo(({remoteContentPolicy, ...props}) => (
const RichText = React.memo(({ remoteContentPolicy, ...props }) => (
<ReactMarkdown
{...props}
renderers={{
link: (props) => {
if (hasProvider(props.href)) {
return <RemoteContent className="mw-100" url={props.href} remoteContentPolicy={remoteContentPolicy}/>;
return <RemoteContent className="mw-100" url={props.href} remoteContentPolicy={remoteContentPolicy} />;
}
return <a {...props} className="bb b--white-d b--black">{props.children}</a>
return <BaseAnchor target='_blank' rel='noreferrer noopener' borderBottom='1px solid' {...props}>{props.children}</BaseAnchor>;
},
paragraph: (props) => {
return <p {...props} className="mb2 lh-copy">{props.children}</p>
return <Text display='block' mb='2' {...props}>{props.children}</Text>;
}
}}
plugins={[[
RemarkDisableTokenizers,
{ block: DISABLED_BLOCK_TOKENS, inline: DISABLED_INLINE_TOKENS }
]]} />
]]}
/>
));
export default RichText;
RichText.displayName = 'RichText';
export default RichText;

View File

@ -2,9 +2,6 @@ import React, { Component } from 'react';
export class SidebarSwitcher extends Component {
render() {
const popoutSwitcher = this.props.popout
? ' dn-m dn-l dn-xl'
: ' dib-m dib-l dib-xl';
const classes = this.props.classes ? this.props.classes : '';
@ -21,7 +18,7 @@ export class SidebarSwitcher extends Component {
}}
>
<img
className={'pr3 dn ' + popoutSwitcher}
className='pr3 dn dib-m dib-l dib-xl'
src={
this.props.sidebarShown
? '/~landscape/img/ChatSwitcherLink.png'

View File

@ -12,9 +12,6 @@ const StatusBar = (props) => {
const location = useLocation();
const atHome = Boolean(location.pathname === '/');
const display = (!window.location.href.includes('popout/'))
? 'grid' : 'none';
const invites = (props.invites && props.invites['/contacts'])
? props.invites['/contacts']
: {};
@ -24,7 +21,7 @@ const StatusBar = (props) => {
return (
<Box
display={display}
display='grid'
width="100%"
gridTemplateRows="30px"
gridTemplateColumns="3fr 1fr"

View File

@ -1,13 +1,14 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Box } from '@tlon/indigo-react';
export const TabBar = (props) => {
const {
location,
settings,
popoutHref
} = props;
let setColor = '', popout = '';
let setColor = '';
if (location.pathname.includes('/settings')) {
setColor = 'black white-d';
@ -15,28 +16,15 @@ export const TabBar = (props) => {
setColor = 'gray3';
}
const hidePopoutIcon = (popout)
? 'dn-m dn-l dn-xl' : 'dib-m dib-l dib-xl';
return (
<div className="dib flex-shrink-0 flex-grow-1">
<div className={'dib pt2 f9 pl6 pr6 lh-solid'}>
<Box display='inline-block' flexShrink='0' flexGrow='1'>
<Box display='inline-block' pt='9px' fontSize='0' pl='16px' pr='6'>
<Link
className={'no-underline ' + setColor}
to={settings}>
Settings
</Link>
</div>
<a href={popoutHref} rel="noopener noreferrer"
target="_blank"
className="dib fr pr1"
style={{ paddingTop: '8px' }}>
<img
className={'flex-shrink-0 pr3 dn ' + hidePopoutIcon}
src="/~chat/img/popout.png"
height="16"
width="16" />
</a>
</div>
</Box>
</Box>
);
};

View File

@ -1,11 +1,12 @@
import React, { Component } from 'react';
import { BaseInput } from '@tlon/indigo-react';
export class OmniboxInput extends Component {
render() {
const { props } = this;
return (
<input
<BaseInput
ref={(el) => {
this.input = el;
if (el && document.activeElement.isSameNode(el)) {
@ -14,8 +15,15 @@ export class OmniboxInput extends Component {
}
}
}
className='ba b--transparent w-100 br2 white-d bg-gray0-d inter f9 pa2'
style={{ maxWidth: 'calc(600px - 1.15rem)', boxSizing: 'border-box' }}
width='100%'
p='2'
backgroundColor='white'
color='black'
border='1px solid transparent'
borderRadius='2'
maxWidth='calc(600px - 1.15rem)'
fontSize='0'
style={{ boxSizing: 'border-box' }}
placeholder='Search...'
onKeyDown={props.control}
onChange={props.search}

View File

@ -1,4 +1,5 @@
import React, { Component } from 'react';
import { Box, Text, Row, BaseInput } from '@tlon/indigo-react';
export class MetadataColor extends Component {
constructor(props) {
@ -39,29 +40,46 @@ export class MetadataColor extends Component {
render() {
const { props, state } = this;
return (
<div className={'cf w-100 mb3 ' + ((props.isDisabled) ? 'o-30' : '')}>
<p className="f8 lh-copy">Change color</p>
<p className="f9 gray2 db mb4">Give this {props.resource} a color when viewing group channels</p>
<div className="relative w-100 flex"
style={{ maxWidth: '10rem' }}
<Box
width='100%'
mb='3'
opacity={(props.isDisabled) ? '0.3' : '1'}
>
<Text my='1' display='block' fontSize='1'>Change color</Text>
<Text fontSize='0' gray display='block' mb='3'>Give this {props.resource} a color when viewing group channels</Text>
<Row
position='relative'
maxWidth='10rem'
width='100%'
>
<div className="absolute"
style={{
height: 16,
width: 16,
backgroundColor: state.color,
top: 13,
left: 11
}} />
<input
className={'pl7 f8 ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
'focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3'}
value={state.color}
disabled={props.isDisabled}
onChange={this.changeColor}
onBlur={this.submitColor} />
</div>
</div>
<Box
position='absolute'
height='16px'
width='16px'
backgroundColor={state.color}
style={{ top: 18, left: 11 }}
/>
<BaseInput
pl='5'
fontSize='1'
border='1px solid'
borderColor='gray'
backgroundColor='white'
pt='3'
pb='3'
pr='3'
display='block'
width='100%'
flex='auto'
color='black'
mr='3'
value={state.color}
disabled={props.isDisabled}
onChange={this.changeColor}
onBlur={this.submitColor}
/>
</Row>
</Box>
);
}
}

View File

@ -1,4 +1,5 @@
import React, { Component } from 'react';
import { Box, Text, Row, BaseInput } from '@tlon/indigo-react';
export class MetadataInput extends Component {
@ -26,13 +27,29 @@ export class MetadataInput extends Component {
} = this.props;
return (
<div className={'w-100 mb3 fl ' + ((isDisabled) ? 'o-30' : '')}>
<p className="f8 lh-copy">{title}</p>
<p className="f9 gray2 db mb4">{description}</p>
<div className="relative w-100 flex" style={{ maxWidth: '29rem' }}>
<input
className={'f8 ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
'focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3'}
<Box
width='100%'
mb='3'
opacity={(isDisabled) ? '0.3' : '1'}
>
<Text display='block' fontSize='1' mb='1'>{title}</Text>
<Text display='block' mb='4' fontSize='0' gray>{description}</Text>
<Row
width='100%'
position='relative'
maxWidth='29rem'
>
<BaseInput
fontSize='1'
border='1px solid'
borderColor='gray'
backgroundColor='white'
p='3'
display='block'
color='black'
width='100'
flex='auto'
mr='3'
type="text"
value={this.state.value}
disabled={isDisabled}
@ -45,8 +62,8 @@ export class MetadataInput extends Component {
}
}}
/>
</div>
</div>
</Row>
</Box>
);
}
}

View File

@ -2,6 +2,7 @@ import React from 'react';
import { MetadataColor } from './color';
import { MetadataInput } from './input';
import { Box } from '@tlon/indigo-react';
import { uxToHex } from '~/logic/lib/util';
@ -27,7 +28,7 @@ export const MetadataSettings = (props) => {
`#${uxToHex(props.association.metadata.color)}` : '';
return (
<div className="cf mt6">
<Box mt='6'>
<MetadataInput
title='Rename'
description={`Change the name of this ${resource}`}
@ -90,7 +91,7 @@ export const MetadataSettings = (props) => {
});
});
}} />
</div>
</Box>
);
};