Merge branch 'release/next-js' into release/hotfix/2021-1-21

This commit is contained in:
Matilde Park 2021-01-21 19:04:47 -05:00
commit 604b338dcd
54 changed files with 3750 additions and 3718 deletions

View File

@ -96,7 +96,7 @@ module.exports = {
]
}
},
exclude: /node_modules/
exclude: /node_modules\/(?!(@tlon\/indigo-dark|@tlon\/indigo-light)\/).*/
},
{
test: /\.css$/i,

View File

@ -26,7 +26,7 @@ module.exports = {
]
}
},
exclude: /node_modules/
exclude: /node_modules\/(?!(@tlon\/indigo-dark|@tlon\/indigo-light)\/).*/
},
{
test: /\.css$/i,

File diff suppressed because it is too large Load Diff

View File

@ -4,83 +4,85 @@
"description": "",
"main": "index.js",
"dependencies": {
"@babel/runtime": "^7.10.5",
"@babel/runtime": "^7.12.5",
"@reach/disclosure": "^0.10.5",
"@reach/menu-button": "^0.10.5",
"@reach/tabs": "^0.10.5",
"@tlon/indigo-light": "^1.0.3",
"@tlon/indigo-react": "1.2.15",
"@tlon/sigil-js": "^1.4.2",
"aws-sdk": "^2.726.0",
"@tlon/indigo-dark": "^1.0.6",
"@tlon/indigo-light": "^1.0.6",
"@tlon/indigo-react": "1.2.17",
"@tlon/sigil-js": "^1.4.3",
"aws-sdk": "^2.830.0",
"big-integer": "^1.6.48",
"classnames": "^2.2.6",
"codemirror": "^5.55.0",
"css-loader": "^3.5.3",
"file-saver": "^2.0.2",
"formik": "^2.1.4",
"immer": "^8.0.0",
"lodash": "^4.17.15",
"codemirror": "^5.59.2",
"css-loader": "^3.6.0",
"file-saver": "^2.0.5",
"formik": "^2.2.6",
"immer": "^8.0.1",
"lodash": "^4.17.20",
"markdown-to-jsx": "^6.11.4",
"moment": "^2.20.1",
"moment": "^2.29.1",
"mousetrap": "^1.6.5",
"mousetrap-global-bind": "^1.1.0",
"normalize-wheel": "1.0.1",
"oembed-parser": "^1.4.1",
"oembed-parser": "^1.4.5",
"prop-types": "^15.7.2",
"react": "^16.5.2",
"react": "^16.14.0",
"react-codemirror2": "^6.0.1",
"react-dom": "^16.8.6",
"react-dom": "^16.14.0",
"react-helmet": "^6.1.0",
"react-markdown": "^4.3.1",
"react-oembed-container": "^1.0.0",
"react-router-dom": "^5.0.0",
"react-virtuoso": "^0.20.0",
"react-router-dom": "^5.2.0",
"react-virtuoso": "^0.20.3",
"react-visibility-sensor": "^5.1.1",
"remark-breaks": "^2.0.1",
"remark-disable-tokenizers": "^1.0.24",
"style-loader": "^1.2.1",
"styled-components": "^5.1.0",
"style-loader": "^1.3.0",
"styled-components": "^5.1.1",
"styled-system": "^5.1.5",
"suncalc": "^1.8.0",
"urbit-ob": "^5.0.0",
"urbit-ob": "^5.0.1",
"yup": "^0.29.3",
"zustand": "^3.2.0"
"zustand": "^3.3.1"
},
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-object-rest-spread": "^7.9.5",
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
"@babel/plugin-transform-runtime": "^7.10.5",
"@babel/preset-env": "^7.9.5",
"@babel/preset-react": "^7.9.4",
"@babel/preset-typescript": "^7.10.1",
"@types/lodash": "^4.14.155",
"@types/react": "^16.9.38",
"@types/react-dom": "^16.9.8",
"@types/react-router-dom": "^5.1.5",
"@types/styled-components": "^5.1.2",
"@babel/core": "^7.12.10",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
"@babel/plugin-proposal-optional-chaining": "^7.12.7",
"@babel/plugin-transform-runtime": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@babel/preset-react": "^7.12.10",
"@babel/preset-typescript": "^7.12.7",
"@types/lodash": "^4.14.168",
"@types/react": "^16.14.2",
"@types/react-dom": "^16.9.10",
"@types/react-router-dom": "^5.1.7",
"@types/styled-components": "^5.1.7",
"@types/styled-system": "^5.1.10",
"@types/yup": "^0.29.7",
"@typescript-eslint/eslint-plugin": "^3.8.0",
"@typescript-eslint/parser": "^3.8.0",
"@types/yup": "^0.29.11",
"@typescript-eslint/eslint-plugin": "^3.10.1",
"@typescript-eslint/parser": "^3.10.1",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"babel-loader": "^8.2.2",
"babel-plugin-lodash": "^3.3.4",
"babel-plugin-root-import": "^6.5.0",
"babel-plugin-root-import": "^6.6.0",
"clean-webpack-plugin": "^3.0.0",
"cross-env": "^7.0.2",
"cross-env": "^7.0.3",
"eslint": "^6.8.0",
"eslint-plugin-react": "^7.19.0",
"file-loader": "^6.0.0",
"html-webpack-plugin": "^4.2.0",
"eslint-plugin-react": "^7.22.0",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^4.5.1",
"moment-locales-webpack-plugin": "^1.2.0",
"react-hot-loader": "^4.12.21",
"sass": "^1.26.5",
"react-hot-loader": "^4.13.0",
"sass": "^1.32.5",
"sass-loader": "^8.0.2",
"typescript": "^3.9.7",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.2"
},
"scripts": {
"lint": "eslint ./src/**/*.{js,ts,tsx}",

View File

@ -1,4 +1,4 @@
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import _ from "lodash";
import f, { memoize } from "lodash/fp";
import bigInt, { BigInteger } from "big-integer";
@ -13,7 +13,7 @@ export const MOMENT_CALENDAR_DATE = {
nextWeek: "dddd",
lastDay: "[Yesterday]",
lastWeek: "[Last] dddd",
sameElse: "DD/MM/YYYY",
sameElse: "~YYYY.M.D",
};
export function appIsGraph(app: string) {
@ -361,4 +361,13 @@ export function pluralize(text: string, isPlural = false, vowel = false) {
export function useShowNickname(contact: Contact | null, hide?: boolean): boolean {
const hideNicknames = typeof hide !== 'undefined' ? hide : useLocalState(state => state.hideNicknames);
return !!(contact && contact.nickname && !hideNicknames);
}
export function useHovering() {
const [hovering, setHovering] = useState(false);
const bind = {
onMouseEnter: () => setHovering(true),
onMouseLeave: () => setHovering(false)
};
return { hovering, bind };
}

View File

@ -386,5 +386,7 @@ function archive(json: any, state: HarkState) {
notifIdxEqual(index, idxNotif.index)
);
state.notifications.set(time, unarchived);
const newlyRead = archived.filter(x => !x.notification.read).length;
updateNotificationStats(state, index, 'notifications', (x) => x - newlyRead);
}
}

View File

@ -31,7 +31,7 @@ const useLocalState = create<LocalState>(persist((set, get) => ({
suspendedFocus: undefined,
toggleOmnibox: () => set(produce(state => {
state.omniboxShown = !state.omniboxShown;
if (state.suspendedFocus) {
if (typeof state.suspendedFocus?.focus === 'function') {
state.suspendedFocus.focus();
state.suspendedFocus = undefined;
} else {

View File

@ -11,8 +11,8 @@ import 'mousetrap-global-bind';
import './css/indigo-static.css';
import './css/fonts.css';
import light from './themes/light';
import dark from './themes/old-dark';
import light from '@tlon/indigo-light';
import dark from '@tlon/indigo-dark';
import { Text, Anchor, Row } from '@tlon/indigo-react';
@ -40,7 +40,7 @@ const Root = styled.div`
background-size: cover;
` : p.background?.type === 'color' ? `
background-color: ${p.background.color};
` : ''
` : `background-color: ${p.theme.colors.white};`
}
display: flex;
flex-flow: column nowrap;
@ -90,7 +90,7 @@ class App extends React.Component {
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
// before the app has actually rendered, hence the timeout.
this.updateTheme(this.themeWatcher);
}, 500);
this.api.local.getBaseHash();

View File

@ -231,8 +231,8 @@ export const MessageWithSigil = (props) => {
}}
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>
<Text flexShrink='0' fontSize='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 =>
@ -259,7 +259,7 @@ const ContentBox = styled(Box)`
export const MessageWithoutSigil = ({ timestamp, contacts, msg, measure, group }) => (
<>
<Text flexShrink={0} mono gray display='inline-block' pt='2px' lineHeight='tall' className="child">{timestamp}</Text>
<Text flexShrink={0} mono gray display='inline-block' pt='2px' lineHeight='tall' className="child" fontSize='0'>{timestamp}</Text>
<ContentBox flexShrink={0} fontSize='14px' className="clamp-message" style={{ flexGrow: 1 }}>
{msg.contents.map((c, i) => (
<MessageContent
@ -282,7 +282,7 @@ export const MessageContent = ({ content, contacts, measure, fontSize, group })
url={content.url}
onLoad={measure}
imageProps={{style: {
maxWidth: '18rem',
maxWidth: 'min(100%,18rem)',
display: 'block'
}}}
videoProps={{style: {

View File

@ -193,6 +193,8 @@ export default class ChatEditor extends Component {
alignItems='center'
flexGrow='1'
height='100%'
paddingTop={MOBILE_BROWSER_REGEX.test(navigator.userAgent) ? '16px' : '0'}
paddingBottom={MOBILE_BROWSER_REGEX.test(navigator.userAgent) ? '16px' : '0'}
maxHeight='224px'
width='calc(100% - 88px)'
className={inCodeMode ? 'chat code' : 'chat'}
@ -201,7 +203,7 @@ export default class ChatEditor extends Component {
{MOBILE_BROWSER_REGEX.test(navigator.userAgent)
? <MobileBox
data-value={this.state.message}
fontSize="14px"
fontSize="1"
lineHeight="tall"
onClick={event => {
if (this.editor) {
@ -211,7 +213,7 @@ export default class ChatEditor extends Component {
>
<BaseTextArea
fontFamily={inCodeMode ? 'Source Code Pro' : 'Inter'}
fontSize="14px"
fontSize="1"
lineHeight="tall"
rows="1"
style={{ width: '100%', background: 'transparent', color: 'currentColor' }}

View File

@ -12,6 +12,7 @@ export default class CodeContent extends Component {
(
<Text
display='block'
fontSize='0'
mono
p='1'
my='0'
@ -37,6 +38,7 @@ export default class CodeContent extends Component {
overflow='auto'
maxHeight='10em'
maxWidth='100%'
fontSize='0'
style={{ whiteSpace: 'pre' }}
>
{content.code.expression}

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import ReactMarkdown from 'react-markdown';
import RemarkDisableTokenizers from 'remark-disable-tokenizers';
import RemarkBreaks from 'remark-breaks';
import urbitOb from 'urbit-ob';
import { Text } from '@tlon/indigo-react';
@ -26,10 +27,10 @@ const DISABLED_INLINE_TOKENS = [
const renderers = {
inlineCode: ({language, value}) => {
return <Text mono p='1' backgroundColor='washedGray' style={{ whiteSpace: 'preWrap'}}>{value}</Text>
return <Text mono p='1' backgroundColor='washedGray' fontSize='0' style={{ whiteSpace: 'preWrap'}}>{value}</Text>
},
paragraph: ({ children }) => {
return (<Text fontSize="14px">{children}</Text>);
return (<Text fontSize="1">{children}</Text>);
},
code: ({language, value}) => {
return <Text
@ -38,6 +39,7 @@ const renderers = {
display='block'
borderRadius='1'
mono
fontSize='0'
backgroundColor='washedGray'
overflowX='auto'
style={{ whiteSpace: 'pre'}}>
@ -66,6 +68,7 @@ const MessageMarkdown = React.memo(props => (
return true;
}}
plugins={[[
RemarkBreaks,
RemarkDisableTokenizers,
{ block: DISABLED_BLOCK_TOKENS, inline: DISABLED_INLINE_TOKENS }
]]} />

View File

@ -277,9 +277,6 @@ pre.CodeMirror-placeholder.CodeMirror-line-like { color: var(--gray); }
/* dark */
@media (prefers-color-scheme: dark) {
body {
background-color: #333;
}
.bg-black-d {
background-color: black;
}

View File

@ -78,7 +78,7 @@ export default function LaunchApp(props) {
color="black"
icon="Mail"
/>
<Text ml="1" mt='1px' color="black">DMs + Drafts</Text>
<Text ml="2" mt='1px' color="black">DMs + Drafts</Text>
</Row>
</Box>
</Tile>
@ -102,7 +102,7 @@ export default function LaunchApp(props) {
icon="CreateGroup"
bg="green"
color="#fff"
text="Create a Group"
text="Create Group"
>
<NewGroup {...props} />
</ModalButton>

View File

@ -20,8 +20,8 @@ export default class BasicTile extends React.PureComponent {
size='12px'
display='inline-block'
verticalAlign='top'
pt='5px'
pr='2px'
mt='5px'
mr='2'
/>
: null
}{props.title}

View File

@ -171,7 +171,7 @@ export default class WeatherTile extends React.Component {
onClick={() => this.setState({ manualEntry: !this.state.manualEntry })}
>
<Box>
<Icon icon='Weather' display='inline-block' verticalAlign='top' pt='3px' pr='2px' />
<Icon icon='Weather' display='inline-block' verticalAlign='top' mt='3px' mr='2' />
<Text>Weather</Text>
</Box>
<Text style={{ cursor: 'pointer' }}>
@ -217,15 +217,14 @@ export default class WeatherTile extends React.Component {
title={`${locationName} Weather`}
>
<Text>
<Icon icon='Weather' display='inline' style={{ position: 'relative', top: '.3em' }} />
Weather
<Icon icon='Weather' display='inline' mr='2' style={{ position: 'relative', top: '.3em' }} />
<Text
cursor='pointer'
onClick={() =>
this.setState({ manualEntry: !this.state.manualEntry })
}
>
->
Weather ->
</Text>
</Text>
@ -268,7 +267,7 @@ export default class WeatherTile extends React.Component {
flexDirection="column"
justifyContent="flex-start"
>
<Text><Icon icon='Weather' color='black' display='inline' style={{ position: 'relative', top: '.3em' }} /> Weather</Text>
<Text><Icon icon='Weather' color='black' display='inline' mr='2' style={{ position: 'relative', top: '.3em' }} /> Weather</Text>
<Text width='100%' display='flex' flexDirection='column' mt={1}>
Loading, please check again later...
</Text>

View File

@ -40,12 +40,12 @@ button {
/* stolen from indigo-react reset.css
* TODO: remove and add reset.css properly
*/
@keyframes loadingSpinnerRotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
@ -53,9 +53,6 @@ button {
/* dark */
@media all and (prefers-color-scheme: dark) {
body {
background-color: #333;
}
.bg-gray0-d {
background-color: #333;
}

View File

@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React, { useEffect, useCallback } from "react";
import { Box, Row, Col, Center, LoadingSpinner, Text } from "@tlon/indigo-react";
import { Switch, Route, Link } from "react-router-dom";
import bigInt from 'big-integer';
@ -10,10 +10,14 @@ import { RouteComponentProps } from "react-router-dom";
import { LinkItem } from "./components/LinkItem";
import LinkSubmit from "./components/LinkSubmit";
import { LinkPreview } from "./components/link-preview";
import { LinkWindow } from "./LinkWindow";
import { Comments } from "~/views/components/Comments";
import "./css/custom.css";
const emptyMeasure = () => {};
type LinkResourceProps = StoreState & {
association: Association;
api: GlobalApi;
@ -57,39 +61,28 @@ export function LinkResource(props: LinkResourceProps) {
return <Center width='100%' height='100%'><LoadingSpinner/></Center>;
}
return (
<Col alignItems="center" height="100%" width="100%" overflowY="auto">
<Col alignItems="center" height="100%" width="100%" overflowY="hidden">
<Switch>
<Route
exact
path={relativePath("")}
render={(props) => {
return (
<Col width="100%" p={4} alignItems="center" maxWidth="768px">
<Col width="100%" flexShrink='0'>
<LinkSubmit s3={s3} name={name} ship={ship.slice(1)} api={api} />
</Col>
{Array.from(graph).map(([date, node]) => {
const contact = contactDetails[node.post.author];
return (
<LinkItem
association={resource}
contacts={contacts}
key={date.toString()}
resource={resourcePath}
node={node}
contacts={contactDetails}
unreads={unreads}
nickname={contact?.nickname}
baseUrl={resourceUrl}
group={group}
path={resource["group-path"]}
api={api}
mb={3}
/>
);
})}
</Col>
<LinkWindow
s3={s3}
association={resource}
contacts={contacts}
resource={resourcePath}
graph={graph}
unreads={unreads}
baseUrl={resourceUrl}
group={group}
path={resource["group-path"]}
api={api}
mb={3}
/>
);
}}
/>
@ -112,6 +105,7 @@ export function LinkResource(props: LinkResourceProps) {
const contact = contactDetails[node.post.author];
return (
<Col alignItems="center" overflowY="auto" width="100%">
<Col width="100%" p={3} maxWidth="768px">
<Link to={resourceUrl}><Text bold>{"<- Back"}</Text></Link>
<LinkItem
@ -125,6 +119,7 @@ export function LinkResource(props: LinkResourceProps) {
path={resource["group-path"]}
api={api}
mt={3}
measure={emptyMeasure}
/>
<Comments
ship={ship}
@ -141,6 +136,7 @@ export function LinkResource(props: LinkResourceProps) {
group={group}
/>
</Col>
</Col>
);
}}
/>

View File

@ -0,0 +1,97 @@
import React, { useRef, useCallback, useEffect, useMemo } from "react";
import { Col } from "@tlon/indigo-react";
import bigInt from 'big-integer';
import {
Association,
Graph,
Contacts,
Unreads,
LocalUpdateRemoteContentPolicy,
Group,
Rolodex,
S3State,
} from "~/types";
import GlobalApi from "~/logic/api/global";
import VirtualScroller from "~/views/components/VirtualScroller";
import { LinkItem } from "./components/LinkItem";
import LinkSubmit from "./components/LinkSubmit";
interface LinkWindowProps {
association: Association;
contacts: Rolodex;
resource: string;
graph: Graph;
unreads: Unreads;
hideNicknames: boolean;
hideAvatars: boolean;
baseUrl: string;
group: Group;
path: string;
api: GlobalApi;
s3: S3State;
}
export function LinkWindow(props: LinkWindowProps) {
const { graph, api, association } = props;
const loadedNewest = useRef(true);
const loadedOldest = useRef(false);
const virtualList = useRef<VirtualScroller>();
const fetchLinks = useCallback(
async (newer: boolean) => {
/* stubbed, should we generalize the display of graphs in virtualscroller? */
}, []
);
useEffect(() => {
const list = virtualList?.current;
if(!list) return;
list.calculateVisibleItems();
}, [graph.size]);
const first = graph.peekLargest()?.[0];
const [,,ship, name] = association['app-path'].split('/');
const style = useMemo(() =>
({
height: "100%",
width: "100%",
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}), []);
return (
<VirtualScroller
ref={(l) => (virtualList.current = l ?? undefined)}
origin="top"
style={style}
onStartReached={() => {}}
onScroll={() => {}}
data={graph}
size={graph.size}
renderer={({ index, measure, scrollWindow }) => {
const node = graph.get(index);
const post = node?.post;
if (!node || !post) return null;
const linkProps = {
...props,
node,
measure,
key: index.toString()
};
if(index.eq(first ?? bigInt.zero)) {
return (
<>
<Col key={index.toString()} mx="auto" mt="4" maxWidth="768px" width="100%" flexShrink='0' px={3}>
<LinkSubmit s3={props.s3} name={name} ship={ship.slice(1)} api={api} />
</Col>
<LinkItem {...linkProps} />
</>
)
}
return <LinkItem {...linkProps} />;
}}
loadRows={fetchLinks}
/>
);
}

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { Link } from 'react-router-dom';
import { Row, Col, Anchor, Box, Text, Icon, Action } from '@tlon/indigo-react';
@ -17,8 +17,9 @@ interface LinkItemProps {
api: GlobalApi;
group: Group;
path: string;
contacts: Rolodex[];
contacts: Rolodex;
unreads: Unreads;
measure: (el: any) => void;
}
export const LinkItem = (props: LinkItemProps) => {
@ -29,9 +30,12 @@ export const LinkItem = (props: LinkItemProps) => {
group,
path,
contacts,
measure,
...rest
} = props;
const ref = useRef<HTMLDivElement | null>(null);
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}/
);
@ -70,9 +74,18 @@ export const LinkItem = (props: LinkItemProps) => {
const markRead = () => {
api.hark.markEachAsRead(props.association, '/', `/${index}`, 'link', 'link');
}
const onMeasure = useCallback(() => {
ref.current && measure(ref.current);
}, [ref.current, measure])
useEffect(() => {
onMeasure();
}, [onMeasure]);
return (
<Box width="100%" {...rest}>
<Box mx="auto" px={3} maxWidth="768px" ref={ref} width="100%" {...rest}>
<Box
lineHeight="tall"
display='flex'
@ -90,6 +103,7 @@ export const LinkItem = (props: LinkItemProps) => {
url={contents[1].url}
text={contents[0].text}
unfold={true}
onLoad={onMeasure}
style={{ alignSelf: 'center' }}
oembedProps={{
p: 2,
@ -116,9 +130,9 @@ export const LinkItem = (props: LinkItemProps) => {
</Anchor>
</Text>
</Box>
<Row minWidth='0' flexShrink={0} width="100%" justifyContent="space-between" py={3} bg="white">
<Author
showImage
contacts={contacts}
@ -136,9 +150,9 @@ export const LinkItem = (props: LinkItemProps) => {
</Box>
</Link>
</Box>
<Dropdown
width="200px"
dropWidth="200px"
alignX="right"
alignY="top"
options={
@ -156,7 +170,7 @@ export const LinkItem = (props: LinkItemProps) => {
>
<Icon ml="2" display="block" icon="Ellipsis" color="gray" />
</Dropdown>
</Row>
</Box>);
};

View File

@ -132,7 +132,6 @@ const LinkSubmit = (props: LinkSubmitProps) => {
position="absolute"
px={2}
pt={2}
fontSize={0}
style={{ pointerEvents: 'none' }}
>{canUpload
? <>
@ -180,7 +179,6 @@ const LinkSubmit = (props: LinkSubmitProps) => {
type="url"
pl={2}
width="100%"
fontSize={0}
py={2}
color="black"
backgroundColor="transparent"
@ -198,8 +196,8 @@ const LinkSubmit = (props: LinkSubmitProps) => {
pl={2}
backgroundColor="transparent"
width="100%"
fontSize={0}
color="black"
fontSize={1}
style={{
resize: 'none',
height: 40

View File

@ -1,105 +0,0 @@
import React, { useCallback } from "react";
import _ from "lodash";
import { Link } from "react-router-dom";
import GlobalApi from "~/logic/api/global";
import {
Rolodex,
Associations,
ChatNotifIndex,
ChatNotificationContents,
Groups,
} from "~/types";
import { BigInteger } from "big-integer";
import { Box, Col } from "@tlon/indigo-react";
import { Header } from "./header";
import { pluralize } from "~/logic/lib/util";
import ChatMessage from "../chat/components/ChatMessage";
function describeNotification(mention: boolean, lent: number) {
const msg = pluralize("message", lent !== 1);
if (mention) {
return `mentioned you in ${msg} in`;
}
return `sent ${msg} in`;
}
export function ChatNotification(props: {
index: ChatNotifIndex;
contents: ChatNotificationContents;
archived: boolean;
read: boolean;
time: number;
timebox: BigInteger;
associations: Associations;
contacts: Rolodex;
groups: Groups;
api: GlobalApi;
}) {
const { index, contents, read, time, api, timebox } = props;
const authors = _.map(contents, "author");
const { chat, mention } = index;
const association = props?.associations?.chat?.[chat];
const groupPath = association?.["group-path"];
const appPath = index?.chat;
const group = props?.groups?.[groupPath];
const desc = describeNotification(mention, contents.length);
const groupContacts = props.contacts[groupPath] || {};
const onClick = useCallback(() => {
if (props.archived) {
return;
}
const func = read ? "unread" : "read";
return api.hark[func](timebox, { chat: index });
}, [api, timebox, index, read]);
return (
<Col onClick={onClick} flexGrow="1" p="2">
<Header
chat
associations={props.associations}
read={read}
archived={props.archived}
time={time}
authors={authors}
moduleIcon="Chat"
channel={chat}
contacts={props.contacts}
group={groupPath}
description={desc}
/>
<Col pb="3" pl="5">
{_.map(_.take(contents, 5), (content, idx) => {
let workspace = groupPath;
if (workspace === undefined || group?.hidden) {
workspace = '/home';
}
const to = `/~landscape${workspace}/resource/chat${appPath}?msg=${content.number}`;
return (
<Link key={idx} to={to}>
<ChatMessage
measure={() => {}}
msg={content}
isLastRead={false}
group={group}
contacts={groupContacts}
fontSize='0'
pt='2'
/>
</Link>
);
})}
{contents.length > 5 && (
<Box ml="4" mt="3" mb="2" color="gray" fontSize="14px">
and {contents.length - 5} other message
{contents.length > 6 ? "s" : ""}
</Box>
)}
</Col>
</Col>
);
}

View File

@ -90,6 +90,8 @@ const GraphNodeContent = ({ group, post, contacts, mod, description, index, remo
content={contents}
group={group}
contacts={contacts}
fontSize='14px'
lineHeight="tall"
/>
} else if (idx[1] === "1") {
const [{ text: header }, { text: body }] = contents;
@ -164,13 +166,14 @@ const GraphNode = ({
group,
read,
onRead,
showContact = false,
remoteContentPolicy
}) => {
const { contents } = post;
author = deSig(author);
const history = useHistory();
const img = (
const img = showContact ? (
<Sigil
ship={`~${author}`}
size={16}
@ -178,7 +181,7 @@ const GraphNode = ({
color={`#000000`}
classes="mix-blend-diff"
/>
);
) : <Box style={{ width: '16px' }}></Box>;
const groupContacts = contacts[groupPath] ?? {};
@ -192,10 +195,10 @@ const GraphNode = ({
}, [read, onRead]);
return (
<Row onClick={onClick} gapX="2" pt="2">
<Row onClick={onClick} gapX="2" pt={showContact ? 2 : 0}>
<Col>{img}</Col>
<Col flexGrow={1} alignItems="flex-start">
<Row
{showContact && <Row
mb="2"
height="16px"
alignItems="center"
@ -208,8 +211,8 @@ const GraphNode = ({
<Text ml="2" gray>
{moment(time).format("HH:mm")}
</Text>
</Row>
<Row width="100%" p="1">
</Row>}
<Row width="100%" p="1" flexDirection="column">
<GraphNodeContent
contacts={groupContacts}
post={post}
@ -244,16 +247,15 @@ export function GraphNotification(props: {
const desc = describeNotification(index.description, contents.length !== 1);
const onClick = useCallback(() => {
if (props.archived) {
if (props.archived || read) {
return;
}
const func = read ? "unread" : "read";
return api.hark[func](timebox, { graph: index });
return api.hark["read"](timebox, { graph: index });
}, [api, timebox, index, read]);
return (
<Col flexGrow={1} width="100%" p="2">
<>
<Header
onClick={onClick}
archived={props.archived}
@ -267,7 +269,7 @@ return (
description={desc}
associations={props.associations}
/>
<Col flexGrow={1} width="100%" pl="5">
<Box flexGrow={1} width="100%" pl={5} gridArea="main">
{_.map(contents, (content, idx) => (
<GraphNode
post={content}
@ -282,9 +284,10 @@ return (
groupPath={group}
read={read}
onRead={onClick}
showContact={idx === 0}
/>
))}
</Col>
</Col>
</Box>
</>
);
}

View File

@ -1,5 +1,5 @@
import React from "react";
import { Text as NormalText, Row, Icon, Rule } from "@tlon/indigo-react";
import { Text as NormalText, Row, Icon, Rule, Box } from "@tlon/indigo-react";
import f from "lodash/fp";
import _ from "lodash";
import moment from "moment";
@ -71,12 +71,13 @@ export function Header(props: {
channel;
return (
<Row onClick={props.onClick} p="2" flexWrap="wrap" gapX="1" alignItems="center">
<Row onClick={props.onClick} p="2" flexWrap="wrap" alignItems="center" gridArea="header">
{!props.archived && (
<Icon
display="block"
mr="1"
icon={read ? "Circle" : "Bullet"}
opacity={read ? 0 : 1}
mr={2}
icon="Bullet"
color="blue"
/>
)}
@ -84,13 +85,13 @@ export function Header(props: {
{authorDesc}
</Text>
<Text mr="1">{description}</Text>
{!!moduleIcon && <Icon icon={moduleIcon as any} />}
{!!channel && <Text fontWeight="500">{channelTitle}</Text>}
<Rule vertical height="12px" />
{!!moduleIcon && <Icon icon={moduleIcon as any} mr={1} />}
{!!channel && <Text fontWeight="500" mr={1}>{channelTitle}</Text>}
<Rule vertical height="12px" mr={1} />
{groupTitle &&
<>
<Text fontWeight="500">{groupTitle}</Text>
<Rule vertical height="12px"/>
<Text fontWeight="500" mr={1}>{groupTitle}</Text>
<Rule vertical height="12px" mr={1} />
</>
}
<Text fontWeight="regular" color="lightGray">

View File

@ -61,20 +61,36 @@ export default function Inbox(props: {
};
}, []);
const [newNotifications, ...notifications] =
const notifications =
Array.from(props.showArchive ? props.archive : props.notifications) || [];
const calendar = {
...MOMENT_CALENDAR_DATE, sameDay: function (now) {
if (this.subtract(6, 'hours').isBefore(now)) {
return "[Earlier Today]";
} else {
return MOMENT_CALENDAR_DATE.sameDay;
}
}
};
const notificationsByDay = f.flow(
let notificationsByDay = f.flow(
f.map<DatedTimebox>(([date, nots]) => [
date,
nots.filter(filterNotification(associations, props.filter)),
]),
f.groupBy<DatedTimebox>(([date]) =>
moment(daToUnix(date)).format("DDMMYYYY")
),
f.values,
f.reverse
f.groupBy<DatedTimebox>(([date]) => {
date = moment(daToUnix(date));
if (moment().subtract(6, 'hours').isBefore(date)) {
return 'latest';
} else {
return date.format("YYYYMMDD");
}
}),
)(notifications);
notificationsByDay = new Map(Object.keys(notificationsByDay).sort().reverse().map(timebox => {
return [timebox, notificationsByDay[timebox]];
}));
useEffect(() => {
api.hark.getMore(props.showArchive);
@ -130,41 +146,27 @@ export default function Inbox(props: {
return (
<Col position="relative" height="100%" overflowY="auto" onScroll={onScroll} >
<Col zIndex={4} gapY={2} bg="white" top="0px" position="sticky">
<Col zIndex={4} gapY={2} bg="white" top="0px" position="sticky" flexShrink={0}>
{inviteItems(invites, api)}
</Col>
{newNotifications && (
<DaySection
latest
timeboxes={[newNotifications]}
contacts={props.contacts}
archive={!!props.showArchive}
associations={props.associations}
groups={props.groups}
graphConfig={props.notificationsGraphConfig}
groupConfig={props.notificationsGroupConfig}
chatConfig={props.notificationsChatConfig}
api={api}
/>
)}
{_.map(
notificationsByDay,
(timeboxes, idx) =>
timeboxes.length > 0 && (
<DaySection
key={idx}
timeboxes={timeboxes}
contacts={props.contacts}
archive={!!props.showArchive}
associations={props.associations}
api={api}
groups={props.groups}
graphConfig={props.notificationsGraphConfig}
groupConfig={props.notificationsGroupConfig}
chatConfig={props.notificationsChatConfig}
/>
)
)}
{[...notificationsByDay.keys()].map((day, index) => {
const timeboxes = notificationsByDay.get(day);
return timeboxes.length > 0 && (
<DaySection
key={day}
label={day === 'latest' ? 'Today' : moment(day).calendar(null, calendar)}
timeboxes={timeboxes}
contacts={props.contacts}
archive={!!props.showArchive}
associations={props.associations}
api={api}
groups={props.groups}
graphConfig={props.notificationsGraphConfig}
groupConfig={props.notificationsGroupConfig}
chatConfig={props.notificationsChatConfig}
/>
);
})}
</Col>
);
}
@ -181,21 +183,18 @@ function sortIndexedNotification(
}
function DaySection({
label,
contacts,
groups,
archive,
timeboxes,
latest = false,
associations,
api,
groupConfig,
graphConfig,
chatConfig,
remoteContentPolicy
}) {
const calendar = latest
? MOMENT_CALENDAR_DATE
: { ...MOMENT_CALENDAR_DATE, sameDay: "[Earlier Today]" };
const lent = timeboxes.map(([,nots]) => nots.length).reduce(f.add, 0);
if (lent === 0 || timeboxes.length === 0) {
return null;
@ -206,7 +205,7 @@ function DaySection({
<Box position="sticky" zIndex="3" top="-1px" bg="white">
<Box p="2" bg="scales.black05">
<Text>
{moment(daToUnix(timeboxes[0][0])).calendar(null, calendar)}
{label}
</Text>
</Box>
</Box>

View File

@ -1,5 +1,5 @@
import React, { ReactNode, useCallback, useMemo } from "react";
import { Row, Box, Col, Text, Anchor, Icon, Action } from "@tlon/indigo-react";
import React, { ReactNode, useCallback, useMemo, useState } from "react";
import { Row, Box } from "@tlon/indigo-react";
import _ from "lodash";
import {
GraphNotificationContents,
@ -7,7 +7,6 @@ import {
GroupNotificationContents,
NotificationGraphConfig,
GroupNotificationsConfig,
NotifIndex,
Groups,
Associations,
Contacts,
@ -17,8 +16,8 @@ import { getParentIndex } from "~/logic/lib/notification";
import { StatelessAsyncAction } from "~/views/components/StatelessAsyncAction";
import { GroupNotification } from "./group";
import { GraphNotification } from "./graph";
import { ChatNotification } from "./chat";
import { BigInteger } from "big-integer";
import { useHovering } from "~/logic/lib/util";
interface NotificationProps {
notification: IndexedNotification;
@ -55,9 +54,6 @@ function getMuted(
if ("group" in index) {
return _.findIndex(groups || [], (g) => g === index.group.group) === -1;
}
if ("chat" in index) {
return _.findIndex(chat || [], (c) => c === index.chat.chat) === -1;
}
return false;
}
@ -89,11 +85,21 @@ function NotificationWrapper(props: {
return api.hark[func](notif);
}, [notif, api, isMuted]);
const { hovering, bind } = useHovering();
const changeMuteDesc = isMuted ? "Unmute" : "Mute";
return (
<Row width="100%" flexShrink={0} alignItems="top" justifyContent="space-between">
<Box
width="100%"
display="grid"
gridTemplateColumns="1fr 200px"
gridTemplateRows="auto"
gridTemplateAreas="'header actions' 'main main'"
pb={2}
{...bind}
>
{children}
<Row gapX="2" p="2" pt='3' alignItems="top">
<Row gapX="2" p="2" pt='3' gridArea="actions" justifyContent="flex-end" opacity={[1, hovering ? 1 : 0]}>
<StatelessAsyncAction name={changeMuteDesc} onClick={onChangeMute} backgroundColor="transparent">
{changeMuteDesc}
</StatelessAsyncAction>
@ -103,7 +109,7 @@ function NotificationWrapper(props: {
</StatelessAsyncAction>
)}
</Row>
</Row>
</Box>
);
}
@ -166,26 +172,6 @@ export function Notification(props: NotificationProps) {
</Wrapper>
);
}
if ("chat" in notification.index) {
const index = notification.index.chat;
const c: ChatNotificationContents = (contents as any).chat;
return (
<Wrapper>
<ChatNotification
api={props.api}
index={index}
contents={c}
contacts={props.contacts}
read={read}
archived={archived}
groups={props.groups}
timebox={props.time}
time={time}
associations={associations}
/>
</Wrapper>
);
}
return null;
}

View File

@ -33,7 +33,7 @@ const SidebarItem = ({ children, view, current }) => {
backgroundColor={selected ? "washedGray" : "white"}
>
<Icon mr={2} display="inline-block" icon={icon(view)} color='black' />
<Text color='black' fontSize={0}>
<Text color='black'>
{children}
</Text>
</Row>
@ -76,7 +76,7 @@ export default function ProfileScreen(props: any) {
height="100%"
width="100%"
display="grid"
gridTemplateColumns={["100%", "200px 1fr"]}
gridTemplateColumns={["100%", "250px 1fr"]}
gridTemplateRows={["48px 1fr", "1fr"]}
borderRadius={1}
bg="white"
@ -95,8 +95,8 @@ export default function ProfileScreen(props: any) {
bg={sigilColor}
borderRadius={8}
my={4}
height={128}
width={128}
height={160}
width={160}
display="flex"
justifyContent="center"
alignItems="center"

View File

@ -10,6 +10,7 @@ import CodeMirror from "codemirror";
import "codemirror/mode/markdown/markdown";
import "codemirror/addon/display/placeholder";
import "codemirror/addon/edit/continuelist";
import "codemirror/lib/codemirror.css";
import { Box } from "@tlon/indigo-react";
@ -54,6 +55,7 @@ export function MarkdownEditor(
scrollbarStyle: "native",
// cursorHeight: 0.85,
placeholder: placeholder || "",
extraKeys: { 'Enter': 'newlineAndIndentContinueMarkdownList' }
};
const editor: React.RefObject<any> = useRef();

View File

@ -67,7 +67,7 @@ export function Note(props: NoteProps & RouteComponentProps) {
color="red"
ml={2}
onClick={deletePost}
css={{ cursor: "pointer" }}
style={{ cursor: "pointer" }}
>
Delete
</Text>

View File

@ -61,9 +61,9 @@ export function NotePreview(props: NotePreviewProps) {
overflow='hidden'
p='2'
>
<WrappedBox mb={2}><Text bold fontSize='0'>{title}</Text></WrappedBox>
<WrappedBox mb={2}><Text bold>{title}</Text></WrappedBox>
<WrappedBox>
<Text fontSize='14px'>
<Text fontSize='14px' lineHeight='tall'>
<ReactMarkdown
unwrapDisallowed
allowedTypes={['text', 'root', 'break', 'paragraph', 'image']}

View File

@ -1,10 +1,9 @@
import React from "react";
import { Link, RouteComponentProps } from "react-router-dom";
import { RouteComponentProps } from "react-router-dom";
import { NotebookPosts } from "./NotebookPosts";
import { Box, Button, Text, Row, Col } from "@tlon/indigo-react";
import { Col } from "@tlon/indigo-react";
import GlobalApi from "~/logic/api/global";
import { Contacts, Rolodex, Groups, Associations, Graph, Association, Unreads } from "~/types";
import { useShowNickname } from "~/logic/lib/util";
interface NotebookProps {
api: GlobalApi;
@ -30,44 +29,14 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
association,
graph
} = props;
const { metadata } = association;
const group = groups[association?.['group-path']];
if (!group) {
return null; // Waiting on groups to populate
}
const relativePath = (p: string) => props.baseUrl + p;
const contact = notebookContacts?.[ship];
const isOwn = `~${window.ship}` === ship;
let isWriter = true;
if (group.tags?.publish?.[`writers-${book}`]) {
isWriter = isOwn || group.tags?.publish?.[`writers-${book}`]?.has(window.ship);
}
const showNickname = useShowNickname(contact);
return (
<Col gapY="4" pt={4} mx="auto" px={3} maxWidth="768px">
<Row justifyContent="space-between">
<Box>
<Text display='block'>{metadata?.title}</Text>
<Text color="lightGray">by </Text>
<Text fontFamily={showNickname ? 'sans' : 'mono'}>
{showNickname ? contact?.nickname : ship}
</Text>
</Box>
{isWriter && (
<Link to={relativePath('/new')}>
<Button primary style={{ cursor: 'pointer' }}>
New Post
</Button>
</Link>
)}
</Row>
<Box borderBottom="1" borderBottomColor="washedGray" />
<NotebookPosts
graph={graph}
host={ship}

View File

@ -206,9 +206,6 @@
}
@media all and (prefers-color-scheme: dark) {
body {
background-color: #333;
}
.bg-black-d {
background-color: black;
}
@ -267,7 +264,7 @@
background-color: #333;
}
.publish .cm-s-tlon.CodeMirror {
background: #333;
background: unset;
color: #fff;
}

View File

@ -29,10 +29,15 @@ export function AsyncButton({
}, [status]);
return (
<Button disabled={!isValid} type="submit" {...rest}>
<Button
hideDisabled={isSubmitting}
disabled={!isValid || isSubmitting}
type="submit"
{...rest}
>
{isSubmitting ? (
<LoadingSpinner
foreground={rest.primary ? "white" : 'black'}
foreground={rest.primary ? "white" : "black"}
background="gray"
/>
) : success === true ? (

View File

@ -52,13 +52,14 @@ export default function Author(props: AuthorProps) {
<Box
ml={showImage ? 2 : 0}
color="black"
fontSize='1'
lineHeight='tall'
fontFamily={showNickname ? "sans" : "mono"}
fontWeight={showNickname ? '500' : '400'}
>
{name}
</Box>
<Box ml={2} color={props.unread ? "blue" : "gray"}>
<Box fontSize='1' ml={2} color={props.unread ? "blue" : "gray"}>
{dateFmt}
</Box>
{props.children}

View File

@ -21,6 +21,7 @@ interface DropdownProps {
alignY: AlignY | AlignY[];
alignX: AlignX | AlignX[];
width?: string;
dropWidth?: string;
}
const ClickBox = styled(Box)`
@ -111,14 +112,14 @@ export function Dropdown(props: DropdownProps) {
});
return (
<Box flexShrink={1} position={open ? "relative" : "static"} minWidth='0'>
<Box flexShrink={1} position={open ? "relative" : "static"} minWidth='0' width={props?.width ? props.width : 'auto'}>
<ClickBox width='100%' ref={anchorRef} onClick={onOpen}>
{children}
</ClickBox>
{open && (
<Portal>
<DropdownOptions
width={props.width || "max-content"}
width={props?.dropWidth || "max-content"}
{...coords}
ref={dropdownRef}
>

View File

@ -19,10 +19,10 @@ interface MentionTextProps {
group: Group;
}
export function MentionText(props: MentionTextProps) {
const { content, contacts, contact, group } = props;
const { content, contacts, contact, group, ...rest } = props;
return (
<RichText contacts={contacts} contact={contact} group={group}>
<RichText contacts={contacts} contact={contact} group={group} {...rest}>
{content.reduce((accum, c) => {
if ("text" in c) {
return accum + c.text;
@ -44,7 +44,7 @@ export function Mention(props: {
const { contacts, ship } = props;
let { contact } = props;
contact = (contact?.nickname) ? contact : contacts?.[ship];
contact = (contact?.color) ? contact : contacts?.[ship];
const showNickname = useShowNickname(contact);

View File

@ -129,7 +129,7 @@ class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
)}
<Text mono gray>{cite(`~${ship}`)}</Text>
{!isOwn && (
<Button mt={2} width="100%" style={{ cursor: 'pointer' }} onClick={() => history.push(`/~landscape/dm/${ship}`)}>
<Button mt={2} fontSize='0' width="100%" style={{ cursor: 'pointer' }} onClick={() => history.push(`/~landscape/dm/${ship}`)}>
Send Message
</Button>
)}

View File

@ -27,19 +27,17 @@ const RichText = React.memo(({ disableRemoteContent, ...props }) => (
{...props}
renderers={{
link: (linkProps) => {
if (disableRemoteContent) {
linkProps.remoteContentPolicy = {
imageShown: false,
audioShown: false,
videoShown: false,
oembedShown: false
};
}
const remoteContentPolicy = disableRemoteContent ? {
imageShown: false,
audioShown: false,
videoShown: false,
oembedShown: false
} : null;
if (hasProvider(linkProps.href)) {
return <RemoteContent className="mw-100" url={linkProps.href} />;
}
return <BaseAnchor target='_blank' rel='noreferrer noopener' borderBottom='1px solid' {...linkProps}>{linkProps.children}</BaseAnchor>;
return <BaseAnchor target='_blank' rel='noreferrer noopener' borderBottom='1px solid' remoteContentPolicy={remoteContentPolicy} {...linkProps}>{linkProps.children}</BaseAnchor>;
},
linkReference: (linkProps) => {
const linkText = String(linkProps.children[0].props.children);

View File

@ -8,6 +8,7 @@ import { useFormikContext } from "formik";
interface AsyncActionProps {
children: ReactNode;
name: string;
disabled?: boolean;
onClick: (e: React.MouseEvent) => Promise<void>;
}
@ -15,6 +16,7 @@ export function StatelessAsyncAction({
children,
onClick,
name = '',
disabled = false,
...rest
}: AsyncActionProps & Parameters<typeof Action>[0]) {
const {
@ -23,7 +25,10 @@ export function StatelessAsyncAction({
} = useStatelessAsyncClickable(onClick, name);
return (
<Action onClick={handleClick} {...rest}>
<Action
hideDisabled={!disabled}
disabled={disabled || state === 'loading'}
onClick={handleClick} {...rest}>
{state === "error" ? (
"Error"
) : state === "loading" ? (

View File

@ -7,7 +7,7 @@ import { useStatelessAsyncClickable } from "~/logic/lib/useStatelessAsyncClickab
interface AsyncButtonProps {
children: ReactNode;
name: string;
name?: string;
onClick: (e: React.MouseEvent) => Promise<void>;
}
@ -15,6 +15,7 @@ export function StatelessAsyncButton({
children,
onClick,
name = "",
disabled = false,
...rest
}: AsyncButtonProps & Parameters<typeof Button>[0]) {
const {
@ -23,7 +24,12 @@ export function StatelessAsyncButton({
} = useStatelessAsyncClickable(onClick, name);
return (
<Button onClick={handleClick} {...rest}>
<Button
hideDisabled={!disabled}
disabled={disabled || state === 'loading'}
onClick={handleClick}
{...rest}
>
{state === "error" ? (
"Error"
) : state === "loading" ? (

View File

@ -5,6 +5,7 @@ import ReconnectButton from './ReconnectButton';
import { StatusBarItem } from './StatusBarItem';
import { Sigil } from '~/logic/lib/sigil';
import useLocalState from '~/logic/state/local';
import { cite } from '~/logic/lib/util';
const StatusBar = (props) => {
const invites = [].concat(...Object.values(props.invites).map(obj => Object.values(obj)));
@ -21,7 +22,7 @@ const StatusBar = (props) => {
pb='3'
>
<Row collapse>
<Button borderColor='washedGray' mr='2' px='2' onClick={() => props.history.push('/')} {...props}>
<Button width="32px" borderColor='washedGray' mr='2' px='2' onClick={() => props.history.push('/')} {...props}>
<Icon icon='Spaces' color='black'/>
</Button>
@ -59,9 +60,8 @@ const StatusBar = (props) => {
>
<Text color='#000000'>Submit <Text color='#000000' display={['none', 'inline']}>an</Text> issue</Text>
</StatusBarItem>
<StatusBarItem px={'2'} flexShrink='0' onClick={() => props.history.push('/~profile')}>
<StatusBarItem width={['32px', 'auto']} px={'2'} flexShrink='0' onClick={() => props.history.push('/~profile')}>
<Sigil ship={props.ship} size={16} color='black' classes='mix-blend-diff' icon />
<Text ml={2} display={["none", "inline"]} fontFamily="mono">~{props.ship}</Text>
</StatusBarItem>
</Row>
</Box>

View File

@ -44,6 +44,8 @@ export default class VirtualScroller extends Component<VirtualScrollerProps, Vir
timeout: ReturnType<typeof setTimeout>;
} | undefined;
overscan = 150;
OVERSCAN_SIZE = 100; // Minimum number of messages on either side before loadRows is called
constructor(props: VirtualScrollerProps) {
@ -53,7 +55,7 @@ export default class VirtualScroller extends Component<VirtualScrollerProps, Vir
visibleItems: new BigIntOrderedMap(),
endgap: props.origin === 'bottom' ? 0 : undefined,
totalHeight: 0,
averageHeight: 64,
averageHeight: 130,
scrollTop: props.origin === 'top' ? 0 : undefined
};
@ -61,8 +63,8 @@ export default class VirtualScroller extends Component<VirtualScrollerProps, Vir
this.window = null;
this.cache = new BigIntOrderedMap();
this.recalculateTotalHeight = this.recalculateTotalHeight.bind(this);
this.calculateVisibleItems = this.calculateVisibleItems.bind(this);
this.recalculateTotalHeight = _.throttle(this.recalculateTotalHeight.bind(this), 200);
this.calculateVisibleItems = _.throttle(this.calculateVisibleItems.bind(this), 200);
this.estimateIndexFromScrollTop = this.estimateIndexFromScrollTop.bind(this);
this.invertedKeyHandler = this.invertedKeyHandler.bind(this);
this.heightOf = this.heightOf.bind(this);
@ -74,6 +76,8 @@ export default class VirtualScroller extends Component<VirtualScrollerProps, Vir
componentDidMount() {
this.calculateVisibleItems();
this.recalculateTotalHeight();
}
componentDidUpdate(prevProps: VirtualScrollerProps, prevState: VirtualScrollerState) {
@ -107,7 +111,7 @@ export default class VirtualScroller extends Component<VirtualScrollerProps, Vir
let { averageHeight } = this.state;
let totalHeight = 0;
this.props.data.forEach((datum, index) => {
totalHeight += this.heightOf(index);
totalHeight += Math.max(this.heightOf(index), 0);
});
averageHeight = Number((totalHeight / this.props.data.size).toFixed());
totalHeight += (this.props.size - this.props.data.size) * averageHeight;
@ -136,41 +140,23 @@ export default class VirtualScroller extends Component<VirtualScrollerProps, Vir
let startgap = 0, heightShown = 0, endgap = 0;
let startGapFilled = false;
let visibleItems = new BigIntOrderedMap<any>();
let startBuffer = new BigIntOrderedMap<any>();
let endBuffer = new BigIntOrderedMap<any>();
const { scrollTop, offsetHeight: windowHeight } = this.window;
const { averageHeight } = this.state;
const { averageHeight, totalHeight } = this.state;
const { data, size: totalSize, onCalculateVisibleItems } = this.props;
console.log(windowHeight);
const overscan = Math.max(windowHeight / 2, 200);
[...data].forEach(([index, datum]) => {
const height = this.heightOf(index);
if (startgap < (scrollTop - overscan) && !startGapFilled) {
startBuffer.set(index, datum);
if (startgap < (scrollTop - this.overscan) && !startGapFilled) {
startgap += height;
} else if (heightShown < (windowHeight + overscan)) {
} else if (heightShown < (windowHeight + this.overscan)) {
startGapFilled = true;
visibleItems.set(index, datum);
heightShown += height;
} else if (endBuffer.size < visibleItems.size) {
endBuffer.set(index, data.get(index));
} else {
endgap += height;
}
});
startBuffer = new BigIntOrderedMap(
[...startBuffer].reverse().slice(0, (visibleItems.size - visibleItems.size % 5))
);
startBuffer.forEach((_datum, index) => {
startgap -= this.heightOf(index);
}
});
endgap = totalHeight - heightShown - startgap;
const firstVisibleKey = visibleItems.peekSmallest()?.[0] ?? this.estimateIndexFromScrollTop(scrollTop)!;
const smallest = data.peekSmallest();
@ -189,7 +175,7 @@ export default class VirtualScroller extends Component<VirtualScrollerProps, Vir
onCalculateVisibleItems ? onCalculateVisibleItems(visibleItems) : null;
this.setState({
startgap: Number(startgap.toFixed()),
visibleItems: new BigIntOrderedMap([...startBuffer, ...visibleItems, ...endBuffer]),
visibleItems,
endgap: Number(endgap.toFixed()),
});
}
@ -238,6 +224,8 @@ export default class VirtualScroller extends Component<VirtualScrollerProps, Vir
}
}
this.overscan = Math.max(element.offsetHeight * 3, 500);
this.window = element;
if (this.props.origin === 'bottom') {
element.addEventListener('wheel', (event) => {
@ -303,7 +291,7 @@ export default class VirtualScroller extends Component<VirtualScrollerProps, Vir
data
} = this.props;
const indexesToRender = visibleItems.keys().reverse();
const indexesToRender = origin === 'top' ? visibleItems.keys() : visibleItems.keys().reverse();
const transform = origin === 'top' ? 'scale3d(1, 1, 1)' : 'scale3d(1, -1, 1)';
@ -314,7 +302,7 @@ export default class VirtualScroller extends Component<VirtualScrollerProps, Vir
height: element.offsetHeight,
element
});
_.debounce(this.recalculateTotalHeight, 500)();
this.recalculateTotalHeight();
}
};
return renderer({ index, measure, scrollWindow: this.window });
@ -322,7 +310,7 @@ export default class VirtualScroller extends Component<VirtualScrollerProps, Vir
return (
<Box overflowY='scroll' ref={this.setWindow.bind(this)} onScroll={this.onScroll.bind(this)} style={{ ...style, ...{ transform } }}>
<Box ref={this.scrollContainer} style={{ transform }}>
<Box ref={this.scrollContainer} style={{ transform, width: '100%' }}>
<Box style={{ height: `${origin === 'top' ? startgap : endgap}px` }}></Box>
{indexesToRender.map(render)}
<Box style={{ height: `${origin === 'top' ? endgap : startgap}px` }}></Box>

View File

@ -22,7 +22,7 @@ export class OmniboxInput extends Component {
border='1px solid transparent'
borderRadius='2'
maxWidth='calc(600px - 1.15rem)'
fontSize='0'
fontSize='1'
style={{ boxSizing: 'border-box' }}
placeholder='Search...'
onKeyDown={props.control}

View File

@ -48,7 +48,7 @@ export function ChannelMenu(props: ChannelMenuProps) {
const isOurs = ship.slice(1) === window.ship;
const isMuted =
const isMuted =
props.graphNotificationConfig.watching.findIndex(
(a) => a.graph === appPath && a.index === "/"
) === -1;
@ -102,7 +102,7 @@ export function ChannelMenu(props: ChannelMenuProps) {
</ChannelMenuItem>
<ChannelMenuItem bottom icon="Gear" color="black">
<Link to={`${baseUrl}/settings`}>
<Box fontSize={0} p="2">
<Box fontSize={1} p="2">
Channel Settings
</Box>
</Link>
@ -119,9 +119,9 @@ export function ChannelMenu(props: ChannelMenuProps) {
}
alignX="right"
alignY="top"
width="250px"
dropWidth="250px"
>
<Icon display="block" icon="Menu" color="gray" />
<Icon display="block" icon="Menu" color="gray" pr='2' />
</Dropdown>
);
}

View File

@ -1,21 +1,18 @@
import React from "react";
import React from 'react';
import {
Center,
Box,
Col,
Row,
Text,
IconButton,
Button,
Icon,
} from "@tlon/indigo-react";
import { uxToHex } from "~/logic/lib/util";
import { Link } from "react-router-dom";
Icon
} from '@tlon/indigo-react';
import { uxToHex } from '~/logic/lib/util';
import { Link } from 'react-router-dom';
import { Association, Associations } from "~/types/metadata-update";
import { Dropdown } from "~/views/components/Dropdown";
import { Workspace } from "~/types";
import { getTitleFromWorkspace } from "~/logic/lib/workspace";
import { Associations } from '~/types/metadata-update';
import { Dropdown } from '~/views/components/Dropdown';
import { Workspace } from '~/types';
import { getTitleFromWorkspace } from '~/logic/lib/workspace';
const GroupSwitcherItem = ({ to, children, bottom = false, ...rest }) => (
<Link to={to}>
@ -47,7 +44,7 @@ function RecentGroups(props: { recent: string[]; associations: Associations }) {
return (e in associations?.contacts);
}).slice(1, 5).map((g) => {
const assoc = associations.contacts[g];
const color = uxToHex(assoc?.metadata?.color || "0x0");
const color = uxToHex(assoc?.metadata?.color || '0x0');
return (
<Link key={g} style={{ minWidth: 0 }} to={`/~landscape${g}`}>
<Row px={1} pb={2} alignItems="center">
@ -60,7 +57,7 @@ function RecentGroups(props: { recent: string[]; associations: Associations }) {
bg={`#${color}`}
mr={2}
display="block"
flexShrink='0'
flexShrink={0}
/>
<Text verticalAlign='top' maxWidth='100%' overflow='hidden' display='inline-block' style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }}>{assoc?.metadata?.title}</Text>
</Row>
@ -76,22 +73,20 @@ export function GroupSwitcher(props: {
workspace: Workspace;
baseUrl: string;
recentGroups: string[];
isAdmin: any;
}) {
const { associations, workspace, isAdmin } = props;
const title = getTitleFromWorkspace(associations, workspace);
const navTo = (to: string) => `${props.baseUrl}${to}`;
return (
<Box zIndex="2" position="sticky" top="0px" p={2}>
<Box height='48px' backgroundColor="white" zIndex="2" position="sticky" top="0px" py={3} pl='3' borderBottom='1px solid' borderColor='washedGray'>
<Col
justifyContent="center"
bg="white"
borderRadius={1}
border={1}
borderColor="washedGray"
>
<Row alignItems="center" justifyContent="space-between">
<Row justifyContent="space-between">
<Dropdown
width="231px"
width="100%"
dropWidth="231px"
alignY="top"
options={
<Col
@ -134,9 +129,9 @@ export function GroupSwitcher(props: {
<Icon mr="2" color="gray" icon="Plus" />
<Text> Join Group</Text>
</GroupSwitcherItem>
{workspace.type === "group" && (
{workspace.type === 'group' && (
<>
<GroupSwitcherItem to={navTo("/popover/participants")}>
<GroupSwitcherItem to={navTo('/popover/participants')}>
<Icon
mr={2}
color="gray"
@ -144,7 +139,7 @@ export function GroupSwitcher(props: {
/>
<Text> Participants</Text>
</GroupSwitcherItem>
<GroupSwitcherItem to={navTo("/popover/settings")}>
<GroupSwitcherItem to={navTo('/popover/settings')}>
<Icon
mr={2}
color="gray"
@ -152,7 +147,7 @@ export function GroupSwitcher(props: {
/>
<Text> Group Settings</Text>
</GroupSwitcherItem>
{isAdmin && (<GroupSwitcherItem bottom to={navTo("/invites")}>
{isAdmin && (<GroupSwitcherItem bottom to={navTo('/invites')}>
<Icon
mr={2}
color="blue"
@ -165,25 +160,25 @@ export function GroupSwitcher(props: {
</Col>
}
>
<Row p={2} alignItems="center" width='100%' minWidth='0'>
<Row alignItems="center" mr={1} flex='1' width='100%' minWidth='0'>
<Text overflow='hidden' display='inline-block' flexShrink='1' style={{ textOverflow: 'ellipsis', whiteSpace: 'pre'}}>{title}</Text>
<Icon size='12px' ml='1' mt="0px" display="inline-block" icon="ChevronSouth" />
<Row width='100%' minWidth='0' flexShrink={0}>
<Row justifyContent="space-between" mr={1} flexShrink={0} width='100%' minWidth='0'>
<Text lineHeight="1.1" fontSize='2' fontWeight="700" overflow='hidden' display='inline-block' flexShrink='1' style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }}>{title}</Text>
</Row>
</Row>
</Dropdown>
<Row pr={1} justifyContent="flex-end" alignItems="center">
{(workspace.type === "group") && (
<Row pr='3' verticalAlign="middle">
{(workspace.type === 'group') && (
<>
{isAdmin && (<Link to={navTo("/invites")}>
{isAdmin && (<Link to={navTo('/invites')}>
<Icon
display="block"
display="inline-block"
color='blue'
icon="Users"
ml='12px'
/>
</Link>)}
<Link to={navTo("/popover/settings")}>
<Icon color='gray' display="block" m={1} icon="Gear" />
<Link to={navTo('/popover/settings')}>
<Icon color='gray' display="inline-block" ml={'12px'} icon="Gear" />
</Link>
</>
)}

View File

@ -85,7 +85,7 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps) {
moduleType
);
}
if (!group) {
await waiter(p => Boolean(p?.groups?.[`/ship/~${window.ship}/${resId}`]));
}
@ -99,11 +99,11 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps) {
actions.setStatus({ error: 'Channel creation failed' });
}
};
return (
<Col overflowY="auto" p={3}>
<Box pb='3' display={['block', 'none']} onClick={() => history.push(props.baseUrl)}>
{'<- Back'}
<Text fontSize='0' bold>{'<- Back'}</Text>
</Box>
<Box fontWeight="bold" mb={4} color="black">
New Channel

View File

@ -80,7 +80,7 @@ export function PopoverRoutes(
<Box
display="grid"
gridTemplateRows={["32px 1fr", "100%"]}
gridTemplateColumns={["100%", "150px 1fr"]}
gridTemplateColumns={["100%", "250px 1fr"]}
height="100%"
width="100%"
>

View File

@ -52,6 +52,7 @@ export function Resource(props: ResourceProps) {
return (
<ResourceSkeleton
baseUrl={props.baseUrl}
groupTags={props.groups?.[selectedGroup]?.tags}
{...skelProps}
>
<ChannelSettings
@ -72,6 +73,7 @@ export function Resource(props: ResourceProps) {
notificationsGraphConfig={props.notificationsGraphConfig}
notificationsChatConfig={props.notificationsChatConfig}
baseUrl={props.baseUrl}
groupTags={props.groups?.[selectedGroup]?.tags}
{...skelProps}
atRoot
>

View File

@ -16,7 +16,7 @@ import { ChannelMenu } from "./ChannelMenu";
import { NotificationGraphConfig } from "~/types";
const TruncatedBox = styled(Box)`
white-space: nowrap;
white-space: pre;
text-overflow: ellipsis;
overflow: hidden;
`;
@ -29,19 +29,33 @@ type ResourceSkeletonProps = {
children: ReactNode;
atRoot?: boolean;
title?: string;
groupTags?: any;
};
export function ResourceSkeleton(props: ResourceSkeletonProps) {
const { association, api, baseUrl, children, atRoot } = props;
const { association, api, baseUrl, children, atRoot, groupTags } = props;
const app = association?.metadata?.module || association["app-name"];
const appPath = association["app-path"];
const workspace =
baseUrl === "/~landscape/home" ? "/home" : association["group-path"];
const title = props.title || association?.metadata?.title;
const [, , ship, resource] = appPath.split("/");
const resourcePath = (p: string) => baseUrl + `/resource/${app}/ship/${ship}/${resource}` + p;
const isOwn = `~${window.ship}` === ship;
let isWriter = (app === 'publish') ? true : false;
if (groupTags?.publish?.[`writers-${resource}`]) {
isWriter = isOwn || groupTags?.publish?.[`writers-${resource}`]?.has(window.ship);
}
return (
<Col width="100%" height="100%" overflowY="hidden">
<Box
flexShrink="0"
height='48px'
py="2"
px="2"
display="flex"
@ -54,9 +68,11 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) {
borderRight={1}
borderRightColor="gray"
pr={3}
fontSize='1'
mr={3}
my="1"
display={["block", "none"]}
flexShrink={0}
>
<Link to={`/~landscape${workspace}`}> {"<- Back"}</Link>
</Box>
@ -70,15 +86,15 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) {
{atRoot && (
<>
<Box pr={1} mr={2}>
<Text display="inline-block" verticalAlign="middle">
<Box px={1} mr={2} minWidth={0} display="flex">
<Text fontSize='2' fontWeight='700' display="inline-block" verticalAlign="middle" textOverflow="ellipsis" overflow="hidden" whiteSpace="pre" minWidth={0}>
{title}
</Text>
</Box>
<TruncatedBox
display={["none", "block"]}
maxWidth="60%"
verticalAlign="middle"
maxWidth='60%'
flexShrink={1}
title={association?.metadata?.description}
color="gray"
@ -93,6 +109,11 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) {
</RichText>
</TruncatedBox>
<Box flexGrow={1} />
{isWriter && (
<Link to={resourcePath('/new')} style={{ flexShrink: '0' }}>
<Text bold pr='3' color='blue'>+ New Post</Text>
</Link>
)}
<ChannelMenu
graphNotificationConfig={props.notificationsGraphConfig}
chatNotificationConfig={props.notificationsChatConfig}

View File

@ -1,26 +1,24 @@
import React, { ReactNode } from "react";
import React, { ReactNode } from 'react';
import styled from 'styled-components';
import {
Box,
Col,
} from "@tlon/indigo-react";
import { Link } from "react-router-dom";
Col
} from '@tlon/indigo-react';
import GlobalApi from "~/logic/api/global";
import { GroupSwitcher } from "../GroupSwitcher";
import GlobalApi from '~/logic/api/global';
import { GroupSwitcher } from '../GroupSwitcher';
import {
Associations,
Workspace,
Groups,
Invites,
Rolodex,
} from "~/types";
import { SidebarListHeader } from "./SidebarListHeader";
import { useLocalStorageState } from "~/logic/lib/useLocalStorageState";
import { getGroupFromWorkspace } from "~/logic/lib/workspace";
Rolodex
} from '~/types';
import { SidebarListHeader } from './SidebarListHeader';
import { useLocalStorageState } from '~/logic/lib/useLocalStorageState';
import { getGroupFromWorkspace } from '~/logic/lib/workspace';
import { SidebarAppConfigs } from './types';
import { SidebarList } from "./SidebarList";
import { roleForShip } from "~/logic/lib/group";
import { SidebarList } from './SidebarList';
import { roleForShip } from '~/logic/lib/group';
const ScrollbarLessCol = styled(Col)`
scrollbar-width: none !important;
@ -30,7 +28,6 @@ const ScrollbarLessCol = styled(Col)`
}
`;
interface SidebarProps {
contacts: Rolodex;
children: ReactNode;
@ -48,38 +45,24 @@ interface SidebarProps {
workspace: Workspace;
}
// Magic spacer that because firefox doesn't correctly calculate
// position: sticky on a flex child
// remove when https://bugzilla.mozilla.org/show_bug.cgi?id=1488080
// is fixed
const SidebarStickySpacer = styled(Box)`
height: 0px;
flex-grow: 1;
@-moz-document url-prefix() {
& {
height: ${p => p.theme.space[6] }px;
}
}
`;
export function Sidebar(props: SidebarProps) {
const { invites, api, associations, selected, apps, workspace } = props;
const { associations, selected, workspace } = props;
const groupPath = getGroupFromWorkspace(workspace);
const display = props.mobileHide ? ["none", "flex"] : "flex";
const display = props.mobileHide ? ['none', 'flex'] : 'flex';
if (!associations) {
return null;
}
const [config, setConfig] = useLocalStorageState<SidebarListConfig>(
`group-config:${groupPath || "home"}`,
`group-config:${groupPath || 'home'}`,
{
sortBy: "lastUpdated",
hideUnjoined: false,
sortBy: 'lastUpdated',
hideUnjoined: false
}
);
const role = props.groups?.[groupPath] ? roleForShip(props.groups[groupPath], window.ship) : undefined;
const isAdmin = (role === "admin") || (workspace?.type === 'home');
const isAdmin = (role === 'admin') || (workspace?.type === 'home');
return (
<ScrollbarLessCol
@ -107,8 +90,9 @@ export function Sidebar(props: SidebarProps) {
groups={props.groups}
initialValues={config}
handleSubmit={setConfig}
selected={selected || ""}
workspace={workspace} />
selected={selected || ''}
workspace={workspace}
/>
<SidebarList
config={config}
associations={associations}
@ -118,31 +102,6 @@ export function Sidebar(props: SidebarProps) {
apps={props.apps}
baseUrl={props.baseUrl}
/>
<SidebarStickySpacer flexShrink={0} />
<Box
flexShrink="0"
display={isAdmin ? "flex" : "none"}
justifyContent="center"
position="sticky"
bottom={"8px"}
width="100%"
height="fit-content"
py="2"
>
<Link
to={!!groupPath ? `/~landscape${groupPath}/new` : `/~landscape/home/new`}
>
<Box
bg="white"
p={2}
borderRadius={1}
border={1}
borderColor="lightGray"
>
+ New Channel
</Box>
</Link>
</Box>
</ScrollbarLessCol>
);
}

View File

@ -93,8 +93,8 @@ export function SidebarItem(props: {
justifyContent="space-between"
alignItems="center"
py={1}
pl={2}
pr={2}
pl={3}
pr={3}
selected={selected}
>
<Row width='100%' alignItems="center" flex='1 auto' minWidth='0'>
@ -105,7 +105,7 @@ export function SidebarItem(props: {
/>
<Box width='100%' flexShrink={2} ml={2} display='flex' overflow='hidden'>
<Text
lineHeight="short"
lineHeight="tall"
display='inline-block'
flex='1'
overflow='hidden'

View File

@ -14,7 +14,8 @@ import { Dropdown } from "~/views/components/Dropdown";
import { FormikHelpers } from "formik";
import { SidebarListConfig, Workspace } from "./types";
import { Link, useHistory } from 'react-router-dom';
import {ShipSearch} from "~/views/components/ShipSearch";
import { getGroupFromWorkspace } from "~/logic/lib/workspace";
import { roleForShip } from "~/logic/lib/group";
import {Groups, Rolodex} from "~/types";
export function SidebarListHeader(props: {
@ -36,42 +37,52 @@ export function SidebarListHeader(props: {
[props.handleSubmit]
);
const groupPath = getGroupFromWorkspace(props.workspace);
const role = props.groups?.[groupPath] ? roleForShip(props.groups[groupPath], window.ship) : undefined;
const isAdmin = (role === "admin") || (props.workspace?.type === 'home');
return (
<Row
flexShrink="0"
alignItems="center"
justifyContent="space-between"
py={2}
pr={2}
pl={2}
px={3}
height='48px'
>
<Box flexShrink='0'>
<Text bold>
<Text>
{props.initialValues.hideUnjoined ? "Joined Channels" : "All Channels"}
</Text>
</Box>
<Box
width='100%'
textAlign='right'
mr='2'
display={(props.workspace?.type === 'home') ? 'inline-block' : 'none'}
display='flex'
alignItems='center'
>
<Link to={`${props.baseUrl}/invites`}>
<Link
style={{
display: isAdmin ? "inline-block" : "none" }}
to={
!!groupPath ? `/~landscape${groupPath}/new` : `/~landscape/home/new`}>
<Icon icon="Plus" color="gray" pr='12px'/>
</Link>
<Link to={`${props.baseUrl}/invites`}
style={{ display: (props.workspace?.type === 'home') ? 'inline-block' : 'none'}}>
<Text
display='inline-block'
verticalAlign='middle'
py='1px'
px='3px'
mr='12px'
backgroundColor='washedBlue'
color='blue'
borderRadius='1'>
+ DM
</Text>
</Link>
</Box>
<Dropdown
flexShrink='0'
width="200px"
width="auto"
alignY="top"
alignX={["right", "left"]}
options={
@ -102,6 +113,7 @@ export function SidebarListHeader(props: {
>
<Icon color="gray" icon="Adjust" />
</Dropdown>
</Box>
</Row>
);
}

View File

@ -1,171 +0,0 @@
import baseStyled, { ThemedStyledInterface } from "styled-components";
const base = {
white: "rgba(255,255,255,1)",
black: "rgba(0,0,0,1)",
red: "rgba(255,65,54,1)",
yellow: "rgba(255,199,0,1)",
green: "rgba(0,159,101,1)",
blue: "rgba(0,142,255,1)",
};
const scales = {
white10: "rgba(255,255,255,0.1)",
white20: "rgba(255,255,255,0.2)",
white30: "rgba(255,255,255,0.3)",
white40: "rgba(255,255,255,0.4)",
white50: "rgba(255,255,255,0.5)",
white60: "rgba(255,255,255,0.6)",
white70: "rgba(255,255,255,0.7)",
white80: "rgba(255,255,255,0.8)",
white90: "rgba(255,255,255,0.9)",
white100: "rgba(255,255,255,1)",
black05: "rgba(0,0,0,0.05)",
black10: "rgba(0,0,0,0.1)",
black20: "rgba(0,0,0,0.2)",
black30: "rgba(0,0,0,0.3)",
black40: "rgba(0,0,0,0.4)",
black50: "rgba(0,0,0,0.5)",
black60: "rgba(0,0,0,0.6)",
black70: "rgba(0,0,0,0.7)",
black80: "rgba(0,0,0,0.8)",
black90: "rgba(0,0,0,0.9)",
black100: "rgba(0,0,0,1)",
red10: "rgba(255,65,54,0.1)",
red20: "rgba(255,65,54,0.2)",
red30: "rgba(255,65,54,0.3)",
red40: "rgba(255,65,54,0.4)",
red50: "rgba(255,65,54,0.5)",
red60: "rgba(255,65,54,0.6)",
red70: "rgba(255,65,54,0.7)",
red80: "rgba(255,65,54,0.8)",
red90: "rgba(255,65,54,0.9)",
red100: "rgba(255,65,54,1)",
yellow10: "rgba(255,199,0,0.1)",
yellow20: "rgba(255,199,0,0.2)",
yellow30: "rgba(255,199,0,0.3)",
yellow40: "rgba(255,199,0,0.4)",
yellow50: "rgba(255,199,0,0.5)",
yellow60: "rgba(255,199,0,0.6)",
yellow70: "rgba(255,199,0,0.7)",
yellow80: "rgba(255,199,0,0.8)",
yellow90: "rgba(255,199,0,0.9)",
yellow100: "rgba(255,199,0,1)",
green10: "rgba(0,159,101,0.1)",
green20: "rgba(0,159,101,0.2)",
green30: "rgba(0,159,101,0.3)",
green40: "rgba(0,159,101,0.4)",
green50: "rgba(0,159,101,0.5)",
green60: "rgba(0,159,101,0.6)",
green70: "rgba(0,159,101,0.7)",
green80: "rgba(0,159,101,0.8)",
green90: "rgba(0,159,101,0.9)",
green100: "rgba(0,159,101,1)",
blue10: "rgba(0,142,255,0.1)",
blue20: "rgba(0,142,255,0.2)",
blue30: "rgba(0,142,255,0.3)",
blue40: "rgba(0,142,255,0.4)",
blue50: "rgba(0,142,255,0.5)",
blue60: "rgba(0,142,255,0.6)",
blue70: "rgba(0,142,255,0.7)",
blue80: "rgba(0,142,255,0.8)",
blue90: "rgba(0,142,255,0.9)",
blue100: "rgba(0,142,255,1)",
};
const theme = {
colors: {
white: base.white,
black: base.black,
darkGray: scales.black80,
gray: scales.black60,
lightGray: scales.black30,
washedGray: scales.black10,
red: base.red,
lightRed: scales.red30,
washedRed: scales.red10,
yellow: base.yellow,
lightYellow: scales.yellow30,
washedYellow: scales.yellow10,
green: base.green,
lightGreen: scales.green30,
washedGreen: scales.green10,
blue: base.blue,
lightBlue: scales.blue30,
washedBlue: scales.blue10,
none: "rgba(0,0,0,0)",
scales: scales,
},
fonts: {
sans: `"Inter", "Inter UI", -apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', Arial, sans-serif`,
mono: `"Source Code Pro", "Roboto mono", "Courier New", monospace`,
},
// font-size
fontSizes: [
12, // 0
16, // 1
24, // 2
32, // 3
48, // 4
64, // 5
],
// font-weight
fontWeights: {
thin: 300,
regular: 400,
bold: 600,
},
// line-height
lineHeights: {
min: 1.2,
short: 1.333333,
regular: 1.5,
tall: 1.666666,
},
// border, border-top, border-right, border-bottom, border-left
borders: ["none", "1px solid"],
// margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, grid-gap, grid-column-gap, grid-row-gap
space: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// border-radius
radii: [
0, // 0
2, // 1
4, // 2
8, // 3
16, // 4
],
// width, height, min-width, max-width, min-height, max-height
sizes: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// z-index
zIndices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
breakpoints: ["550px", "750px", "960px"],
};
export type Theme = typeof theme;
export const styled = baseStyled as ThemedStyledInterface<Theme>;
export default theme;

View File

@ -1,186 +0,0 @@
import baseStyled, { ThemedStyledInterface } from "styled-components";
const base = {
white: "rgba(255,255,255,1)",
black: "rgba(0,0,0,1)",
red: "rgba(255,65,54,1)",
yellow: "rgba(255,199,0,1)",
green: "rgba(0,159,101,1)",
blue: "rgba(0,142,255,1)",
};
const scales = {
white05: "rgba(255,255,255,0.05)",
white10: "rgba(255,255,255,0.1)",
white20: "rgba(255,255,255,0.2)",
white30: "rgba(255,255,255,0.3)",
white40: "rgba(255,255,255,0.4)",
white50: "rgba(255,255,255,0.5)",
white60: "rgba(255,255,255,0.6)",
white70: "rgba(255,255,255,0.7)",
white80: "rgba(255,255,255,0.8)",
white90: "rgba(255,255,255,0.9)",
white100: "rgba(255,255,255,1)",
black05: "rgba(0,0,0,0.05)",
black10: "rgba(0,0,0,0.1)",
black20: "rgba(0,0,0,0.2)",
black30: "rgba(0,0,0,0.3)",
black40: "rgba(0,0,0,0.4)",
black50: "rgba(0,0,0,0.5)",
black60: "rgba(0,0,0,0.6)",
black70: "rgba(0,0,0,0.7)",
black80: "rgba(0,0,0,0.8)",
black90: "rgba(0,0,0,0.9)",
black100: "rgba(0,0,0,1)",
red05: "rgba(255,65,54,0.05)",
red10: "rgba(255,65,54,0.1)",
red20: "rgba(255,65,54,0.2)",
red30: "rgba(255,65,54,0.3)",
red40: "rgba(255,65,54,0.4)",
red50: "rgba(255,65,54,0.5)",
red60: "rgba(255,65,54,0.6)",
red70: "rgba(255,65,54,0.7)",
red80: "rgba(255,65,54,0.8)",
red90: "rgba(255,65,54,0.9)",
red100: "rgba(255,65,54,1)",
yellow05: "rgba(255,199,0,0.05)",
yellow10: "rgba(255,199,0,0.1)",
yellow20: "rgba(255,199,0,0.2)",
yellow30: "rgba(255,199,0,0.3)",
yellow40: "rgba(255,199,0,0.4)",
yellow50: "rgba(255,199,0,0.5)",
yellow60: "rgba(255,199,0,0.6)",
yellow70: "rgba(255,199,0,0.7)",
yellow80: "rgba(255,199,0,0.8)",
yellow90: "rgba(255,199,0,0.9)",
yellow100: "rgba(255,199,0,1)",
green05: "rgba(0,159,101,0.05)",
green10: "rgba(0,159,101,0.1)",
green20: "rgba(0,159,101,0.2)",
green30: "rgba(0,159,101,0.3)",
green40: "rgba(0,159,101,0.4)",
green50: "rgba(0,159,101,0.5)",
green60: "rgba(0,159,101,0.6)",
green70: "rgba(0,159,101,0.7)",
green80: "rgba(0,159,101,0.8)",
green90: "rgba(0,159,101,0.9)",
green100: "rgba(0,159,101,1)",
blue05: "rgba(0,142,255,0.05)",
blue10: "rgba(0,142,255,0.1)",
blue20: "rgba(0,142,255,0.2)",
blue30: "rgba(0,142,255,0.3)",
blue40: "rgba(0,142,255,0.4)",
blue50: "rgba(0,142,255,0.5)",
blue60: "rgba(0,142,255,0.6)",
blue70: "rgba(0,142,255,0.7)",
blue80: "rgba(0,142,255,0.8)",
blue90: "rgba(0,142,255,0.9)",
blue100: "rgba(0,142,255,1)",
};
const util = {
cyan: "#00FFFF",
magenta: "#FF00FF",
yellow: "#FFFF00",
black: "#000000",
gray0: "#333333"
};
const theme = {
colors: {
white: util.gray0,
black: base.white,
darkGray: scales.white80,
gray: scales.white60,
lightGray: scales.white30,
washedGray: scales.white05,
red: base.red,
lightRed: scales.red30,
washedRed: scales.red05,
yellow: base.yellow,
lightYellow: scales.yellow30,
washedYellow: scales.yellow10,
green: base.green,
lightGreen: scales.green30,
washedGreen: scales.green10,
blue: base.blue,
lightBlue: scales.blue30,
washedBlue: scales.blue10,
none: "rgba(0,0,0,0)",
scales: scales,
util: util,
},
fonts: {
sans: `"Inter", "Inter UI", -apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', Arial, sans-serif`,
mono: `"Source Code Pro", "Roboto mono", "Courier New", monospace`,
},
// font-size
fontSizes: [
12, // 0
16, // 1
24, // 2
32, // 3
48, // 4
64, // 5
],
// font-weight
fontWeights: {
thin: 300,
regular: 400,
semibold: 500,
bold: 600,
},
// line-height
lineHeights: {
min: 1.2,
short: 1.333333,
regular: 1.5,
tall: 1.666666,
},
// border, border-top, border-right, border-bottom, border-left
borders: ["none", "1px solid"],
// margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, grid-gap, grid-column-gap, grid-row-gap
space: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// border-radius
radii: [
0, // 0
2, // 1
4, // 2
8, // 3
],
// width, height, min-width, max-width, min-height, max-height
sizes: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// z-index
zIndices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
breakpoints: ["550px", "750px", "960px"],
};
export type Theme = typeof theme;
export const styled = baseStyled as ThemedStyledInterface<Theme>;
export default theme;