mirror of
https://github.com/urbit/shrub.git
synced 2024-11-28 13:54:20 +03:00
Merge branch 'release/next-js' into release/hotfix/2021-1-21
This commit is contained in:
commit
604b338dcd
@ -96,7 +96,7 @@ module.exports = {
|
||||
]
|
||||
}
|
||||
},
|
||||
exclude: /node_modules/
|
||||
exclude: /node_modules\/(?!(@tlon\/indigo-dark|@tlon\/indigo-light)\/).*/
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
|
@ -26,7 +26,7 @@ module.exports = {
|
||||
]
|
||||
}
|
||||
},
|
||||
exclude: /node_modules/
|
||||
exclude: /node_modules\/(?!(@tlon\/indigo-dark|@tlon\/indigo-light)\/).*/
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
|
6052
pkg/interface/package-lock.json
generated
6052
pkg/interface/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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}",
|
||||
|
@ -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 };
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
|
@ -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: {
|
||||
|
@ -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' }}
|
||||
|
@ -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}
|
||||
|
@ -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 }
|
||||
]]} />
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
97
pkg/interface/src/views/apps/links/LinkWindow.tsx
Normal file
97
pkg/interface/src/views/apps/links/LinkWindow.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
@ -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>);
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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();
|
||||
|
@ -67,7 +67,7 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
color="red"
|
||||
ml={2}
|
||||
onClick={deletePost}
|
||||
css={{ cursor: "pointer" }}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
Delete
|
||||
</Text>
|
||||
|
@ -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']}
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 ? (
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
>
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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>
|
||||
)}
|
||||
|
@ -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);
|
||||
|
@ -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" ? (
|
||||
|
@ -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" ? (
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
</>
|
||||
)}
|
||||
|
@ -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
|
||||
|
@ -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%"
|
||||
>
|
||||
|
@ -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
|
||||
>
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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'
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
@ -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;
|
Loading…
Reference in New Issue
Block a user