mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-09-20 15:08:34 +03:00
Merge branch 'release/next-js' into release/next-userspace
This commit is contained in:
commit
d3c52c5e19
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -62,7 +62,7 @@ jobs:
|
||||
|
||||
- uses: cachix/cachix-action@v8
|
||||
with:
|
||||
name: ${{ secrets.CACHIX_NAME }}
|
||||
name: ares
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
||||
- run: nix-build -A urbit --arg enableStatic true
|
||||
@ -88,7 +88,7 @@ jobs:
|
||||
- uses: cachix/install-nix-action@v12
|
||||
- uses: cachix/cachix-action@v8
|
||||
with:
|
||||
name: ${{ secrets.CACHIX_NAME }}
|
||||
name: ares
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
||||
- run: nix-build -A hs.urbit-king.components.exes.urbit-king --arg enableStatic true
|
||||
|
2
.github/workflows/release-docker.yml
vendored
2
.github/workflows/release-docker.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
system-features = nixos-test benchmark big-parallel kvm
|
||||
- uses: cachix/cachix-action@v8
|
||||
with:
|
||||
name: ${{ secrets.CACHIX_NAME }}
|
||||
name: ares
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
- uses: docker/docker-login-action@v1.8.0
|
||||
with:
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:288b3ab68e2e3946dbf0e0de05c2907c351ec97fcc08134e558569eab4121c94
|
||||
size 8809816
|
||||
oid sha256:271d575a87373f4ed73b195780973ed41cb72be21b428a645c42a49ab5f786ee
|
||||
size 8873583
|
||||
|
@ -5,7 +5,7 @@
|
||||
/- glob
|
||||
/+ default-agent, verb, dbug
|
||||
|%
|
||||
++ hash 0v1.4u9gp.rs1fi.ki7ok.ib4cp.mgdvs
|
||||
++ hash 0v7.ttn7o.50403.rf6oh.63hnc.hgpc9
|
||||
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
|
||||
+$ all-states
|
||||
$% state-0
|
||||
|
@ -24,6 +24,6 @@
|
||||
<div id="portal-root"></div>
|
||||
<script src="/~landscape/js/channel.js"></script>
|
||||
<script src="/~landscape/js/session.js"></script>
|
||||
<script src="/~landscape/js/bundle/index.86c6e416c338a305e1e9.js"></script>
|
||||
<script src="/~landscape/js/bundle/index.7d4248944fe1255cb74b.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -180,6 +180,9 @@
|
||||
$(index +(index), sorted [(~(got by fragments) index) sorted])
|
||||
::
|
||||
(cue (rep 13 (flop sorted)))
|
||||
:: +jim: caching +jam
|
||||
::
|
||||
++ jim |=(n=* ~+((jam n)))
|
||||
:: +bind-duct: find or make new $bone for .duct in .ossuary
|
||||
::
|
||||
++ bind-duct
|
||||
@ -1931,7 +1934,7 @@
|
||||
==
|
||||
now
|
||||
::
|
||||
=/ =message-blob (dedup-message (jam payload))
|
||||
=/ =message-blob (dedup-message (jim payload))
|
||||
=. peer-core (run-message-pump bone %memo message-blob)
|
||||
::
|
||||
?: &(=(%boon valence) ?=(?(%dead %unborn) -.qos.peer-state))
|
||||
|
@ -29,13 +29,16 @@
|
||||
^- form:m
|
||||
=/ pax
|
||||
(en-path:resource rid)
|
||||
=/ hold=@dr ~s0..8000
|
||||
|- ^- form:m
|
||||
?> (lte hold ~m5)
|
||||
=* loop $
|
||||
;< u-group=(unit group) bind:m
|
||||
(scry:strandio ,(unit group) (weld /gx/group-store/groups (snoc pax %noun)))
|
||||
?^ u-group
|
||||
(pure:m ~)
|
||||
;< ~ bind:m (sleep:strandio `@dr`(div ~s1 2))
|
||||
;< ~ bind:m (sleep:strandio hold)
|
||||
=. hold (mul hold 2)
|
||||
loop
|
||||
::
|
||||
++ wait-for-md
|
||||
@ -44,13 +47,16 @@
|
||||
^- form:m
|
||||
=/ pax
|
||||
(en-path:resource rid)
|
||||
=/ hold=@dr ~s0..8000
|
||||
|- ^- form:m
|
||||
?> (lte hold ~m5)
|
||||
=* loop $
|
||||
;< groups=(set path) bind:m
|
||||
(scry:strandio ,(set path) /gy/metadata-store/group-indices)
|
||||
?: (~(has in groups) pax)
|
||||
;< groups=(jug path md-resource) bind:m
|
||||
(scry:strandio ,(jug path md-resource) /gy/metadata-store/group-indices)
|
||||
?: (~(has by groups) pax)
|
||||
(pure:m ~)
|
||||
;< ~ bind:m (sleep:strandio `@dr`(div ~s1 2))
|
||||
;< ~ bind:m (sleep:strandio hold)
|
||||
=. hold (mul hold 2)
|
||||
loop
|
||||
--
|
||||
::
|
||||
|
BIN
pkg/interface/package-lock.json
generated
BIN
pkg/interface/package-lock.json
generated
Binary file not shown.
@ -4,85 +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-dark": "^1.0.6",
|
||||
"@tlon/indigo-light": "^1.0.6",
|
||||
"@tlon/indigo-react": "1.2.16",
|
||||
"@tlon/indigo-react": "1.2.17",
|
||||
"@tlon/sigil-js": "^1.4.3",
|
||||
"aws-sdk": "^2.726.0",
|
||||
"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}",
|
||||
|
@ -3,8 +3,8 @@ import { StoreState } from '../store/type';
|
||||
import { Patp, Path, PatpNoSig } from '~/types/noun';
|
||||
import _ from 'lodash';
|
||||
import {makeResource, resourceFromPath} from '../lib/group';
|
||||
import {GroupPolicy, Enc, Post, NodeMap, Content} from '~/types';
|
||||
import { numToUd, unixToDa, decToUd, deSig } from '~/logic/lib/util';
|
||||
import {GroupPolicy, Enc, Post, NodeMap, Content, Resource} from '~/types';
|
||||
import { numToUd, unixToDa, decToUd, deSig, resourceAsPath } from '~/logic/lib/util';
|
||||
|
||||
export const createBlankNodeWithChildPost = (
|
||||
parentIndex: string = '',
|
||||
@ -81,6 +81,8 @@ function moduleToMark(mod: string): string | undefined {
|
||||
|
||||
export default class GraphApi extends BaseApi<StoreState> {
|
||||
|
||||
joiningGraphs = new Set<string>();
|
||||
|
||||
private storeAction(action: any): Promise<any> {
|
||||
return this.action('graph-store', 'graph-update', action)
|
||||
}
|
||||
@ -138,11 +140,19 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
|
||||
joinGraph(ship: Patp, name: string) {
|
||||
const resource = makeResource(ship, name);
|
||||
const rid = resourceAsPath(resource);
|
||||
if(this.joiningGraphs.has(rid)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
this.joiningGraphs.add(rid);
|
||||
return this.viewAction('graph-join', {
|
||||
join: {
|
||||
resource,
|
||||
ship,
|
||||
}
|
||||
}).then(res => {
|
||||
this.joiningGraphs.delete(rid);
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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: {
|
||||
|
@ -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');
|
||||
}
|
||||
return (
|
||||
<Box width="100%" {...rest}>
|
||||
|
||||
|
||||
const onMeasure = useCallback(() => {
|
||||
ref.current && measure(ref.current);
|
||||
}, [ref.current, measure])
|
||||
|
||||
useEffect(() => {
|
||||
onMeasure();
|
||||
}, [onMeasure]);
|
||||
|
||||
return (
|
||||
<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,
|
||||
|
@ -146,7 +146,7 @@ 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>
|
||||
{[...notificationsByDay.keys()].map((day, index) => {
|
||||
|
@ -264,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 ? (
|
||||
|
@ -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" ? (
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -72,6 +72,7 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) {
|
||||
mr={3}
|
||||
my="1"
|
||||
display={["block", "none"]}
|
||||
flexShrink={0}
|
||||
>
|
||||
<Link to={`/~landscape${workspace}`}> {"<- Back"}</Link>
|
||||
</Box>
|
||||
@ -85,8 +86,8 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) {
|
||||
|
||||
{atRoot && (
|
||||
<>
|
||||
<Box pr={1} mr={2}>
|
||||
<Text fontSize='2' fontWeight='700' display="inline-block" verticalAlign="middle" textOverflow="ellipsis" overflow="hidden" whiteSpace="pre">
|
||||
<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>
|
||||
|
@ -58,13 +58,14 @@ export function SidebarListHeader(props: {
|
||||
<Box
|
||||
textAlign='right'
|
||||
display='flex'
|
||||
alignItems='center'
|
||||
>
|
||||
<Link
|
||||
style={{
|
||||
display: isAdmin ? "inline-block" : "none" }}
|
||||
to={
|
||||
!!groupPath ? `/~landscape${groupPath}/new` : `/~landscape/home/new`}>
|
||||
<Icon icon="Plus" color="gray" pr='2'/>
|
||||
<Icon icon="Plus" color="gray" pr='12px'/>
|
||||
</Link>
|
||||
<Link to={`${props.baseUrl}/invites`}
|
||||
style={{ display: (props.workspace?.type === 'home') ? 'inline-block' : 'none'}}>
|
||||
@ -72,7 +73,7 @@ export function SidebarListHeader(props: {
|
||||
display='inline-block'
|
||||
py='1px'
|
||||
px='3px'
|
||||
mr='2'
|
||||
mr='12px'
|
||||
backgroundColor='washedBlue'
|
||||
color='blue'
|
||||
borderRadius='1'>
|
||||
|
Loading…
Reference in New Issue
Block a user