mirror of
https://github.com/wassname/talk.git
synced 2026-07-13 17:45:56 +08:00
Use pagination container
This commit is contained in:
@@ -1,10 +1,23 @@
|
||||
import {
|
||||
ConnectionConfig,
|
||||
ConnectionConfig as OrigConnectionConfig,
|
||||
createPaginationContainer,
|
||||
GraphQLTaggedNode,
|
||||
PageInfo,
|
||||
RelayPaginationProp,
|
||||
} from "react-relay";
|
||||
import { InferableComponentEnhancerWithProps } from "recompose";
|
||||
import { Overwrite } from "talk-framework/types";
|
||||
|
||||
// TODO: (cvle) Fix this type definition upstream.
|
||||
interface ConnectionData {
|
||||
edges?: Readonly<any>;
|
||||
pageInfo?: PageInfo;
|
||||
}
|
||||
|
||||
type ConnectionConfig<T> = Overwrite<
|
||||
OrigConnectionConfig<T>,
|
||||
{ getConnectionFromProps?(props: T): ConnectionData | undefined | null }
|
||||
>;
|
||||
|
||||
/**
|
||||
* withPaginationContainer is a curried version of `createPaginationContainers`
|
||||
|
||||
@@ -4,17 +4,11 @@ import { StatelessComponent } from "react";
|
||||
import { Center } from "talk-ui/components";
|
||||
|
||||
import AssetListContainer from "../containers/AssetListContainer";
|
||||
import PostCommentFormContainer from "../containers/PostCommentFormContainer";
|
||||
import StreamContainer from "../containers/StreamContainer";
|
||||
import Logo from "./Logo";
|
||||
|
||||
export interface AppProps {
|
||||
assets?: any | null;
|
||||
asset?: {
|
||||
id: string;
|
||||
isClosed: boolean;
|
||||
comments: any | null;
|
||||
} | null;
|
||||
asset?: {} | null;
|
||||
}
|
||||
|
||||
const App: StatelessComponent<AppProps> = props => {
|
||||
@@ -24,9 +18,7 @@ const App: StatelessComponent<AppProps> = props => {
|
||||
if (props.asset) {
|
||||
return (
|
||||
<Center>
|
||||
<Logo gutterBottom />
|
||||
<StreamContainer comments={props.asset.comments} />
|
||||
<PostCommentFormContainer assetID={props.asset.id} />
|
||||
<StreamContainer asset={props.asset} />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
margin-bottom: calc(2px * $spacing-unit);
|
||||
}
|
||||
|
||||
.postButton {
|
||||
float: right;
|
||||
}
|
||||
.postButtonContainer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
@@ -39,11 +39,13 @@ const PostCommentForm: StatelessComponent<PostCommentFormProps> = props => (
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<Localized id="postCommentForm-submit">
|
||||
<Button className={styles.postButton} disabled={submitting} primary>
|
||||
Post
|
||||
</Button>
|
||||
</Localized>
|
||||
<div className={styles.postButtonContainer}>
|
||||
<Localized id="postCommentForm-submit">
|
||||
<Button disabled={submitting} primary>
|
||||
Post
|
||||
</Button>
|
||||
</Localized>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</Form>
|
||||
|
||||
@@ -1,18 +1,36 @@
|
||||
import * as React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
|
||||
import CommentContainer from "../containers/CommentContainer";
|
||||
import Logo from "talk-stream/components/Logo";
|
||||
import CommentContainer from "talk-stream/containers/CommentContainer";
|
||||
import PostCommentFormContainer from "talk-stream/containers/PostCommentFormContainer";
|
||||
import { Button } from "talk-ui/components";
|
||||
|
||||
export interface StreamProps {
|
||||
comments: ReadonlyArray<{ id: string }>;
|
||||
id: string;
|
||||
isClosed: boolean;
|
||||
comments: ReadonlyArray<{ id: string }> | null;
|
||||
onLoadMore: () => void;
|
||||
hasMore: boolean;
|
||||
}
|
||||
|
||||
const Stream: StatelessComponent<StreamProps> = props => {
|
||||
if (props.comments === null) {
|
||||
// TODO: (@cvle) What's the reason for this being null?
|
||||
return <div>Comments unavailable</div>;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Logo gutterBottom />
|
||||
<PostCommentFormContainer assetID={props.id} />
|
||||
{props.comments.map(comment => (
|
||||
<CommentContainer key={comment.id} data={comment} gutterBottom />
|
||||
))}
|
||||
{props.hasMore && (
|
||||
<Button onClick={props.onLoadMore} secondary fullWidth>
|
||||
Load More
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -26,11 +26,7 @@ const enhanced = withFragmentContainer<{ data: Data }>(
|
||||
...AssetListContainer_assets
|
||||
}
|
||||
asset(id: $assetID) @skip(if: $showAssetList) {
|
||||
id
|
||||
isClosed
|
||||
comments {
|
||||
...StreamContainer_comments
|
||||
}
|
||||
...StreamContainer_asset
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@@ -25,6 +25,8 @@ class PostCommentFormContainer extends Component<InnerProps> {
|
||||
if (error instanceof BadUserInputError) {
|
||||
return error.invalidArgsLocalized;
|
||||
}
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(error);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
@@ -1,32 +1,108 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { graphql } from "react-relay";
|
||||
import React from "react";
|
||||
import { graphql, RelayPaginationProp } from "react-relay";
|
||||
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import { withPaginationContainer } from "talk-framework/lib/relay";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { StreamContainer_comments as Data } from "talk-stream/__generated__/StreamContainer_comments.graphql";
|
||||
import { StreamContainer_asset as Data } from "talk-stream/__generated__/StreamContainer_asset.graphql";
|
||||
|
||||
import Stream from "../components/Stream";
|
||||
|
||||
interface InnerProps {
|
||||
comments: Data;
|
||||
asset: Data;
|
||||
relay: RelayPaginationProp;
|
||||
}
|
||||
|
||||
const StreamContainer: StatelessComponent<InnerProps> = props => {
|
||||
const comments = props.comments.edges.map(edge => edge.node);
|
||||
return <Stream comments={comments} />;
|
||||
};
|
||||
class StreamContainer extends React.Component<InnerProps> {
|
||||
public render() {
|
||||
const comments = this.props.asset.comments
|
||||
? this.props.asset.comments.edges.map(edge => edge.node)
|
||||
: null;
|
||||
return (
|
||||
<Stream
|
||||
{...this.props.asset}
|
||||
comments={comments}
|
||||
onLoadMore={this.loadMore}
|
||||
hasMore={this.props.relay.hasMore()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<{ comments: Data }>(
|
||||
private loadMore = () => {
|
||||
if (!this.props.relay.hasMore() || this.props.relay.isLoading()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.relay.loadMore(
|
||||
10, // Fetch the next 10 feed items
|
||||
error => {
|
||||
if (error) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const enhanced = withPaginationContainer<{ asset: Data }, InnerProps>(
|
||||
graphql`
|
||||
fragment StreamContainer_comments on CommentsConnection {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...CommentContainer
|
||||
fragment StreamContainer_asset on Asset
|
||||
@argumentDefinitions(
|
||||
count: { type: "Int", defaultValue: 5 }
|
||||
cursor: { type: "Cursor" }
|
||||
orderBy: { type: "COMMENT_SORT", defaultValue: CREATED_AT_DESC }
|
||||
) {
|
||||
id
|
||||
isClosed
|
||||
comments(first: $count, after: $cursor, orderBy: $orderBy)
|
||||
@connection(key: "Stream_comments") {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...CommentContainer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
`,
|
||||
{
|
||||
direction: "forward",
|
||||
getConnectionFromProps(props) {
|
||||
return props.asset && props.asset.comments;
|
||||
},
|
||||
// This is also the default implementation of `getFragmentVariables` if it isn't provided.
|
||||
getFragmentVariables(prevVars, totalCount) {
|
||||
return {
|
||||
...prevVars,
|
||||
count: totalCount,
|
||||
};
|
||||
},
|
||||
getVariables(props, { count, cursor }, fragmentVariables) {
|
||||
return {
|
||||
count,
|
||||
cursor,
|
||||
orderBy: fragmentVariables.orderBy,
|
||||
// assetID isn't specified as an @argument for the fragment, but it should be a
|
||||
// variable available for the fragment under the query root.
|
||||
assetID: props.asset.id,
|
||||
};
|
||||
},
|
||||
query: graphql`
|
||||
# Pagination query to be fetched upon calling 'loadMore'.
|
||||
# Notice that we re-use our fragment, and the shape of this query matches our fragment spec.
|
||||
query StreamContainerPaginationQuery(
|
||||
$count: Int!
|
||||
$cursor: Cursor
|
||||
$orderBy: COMMENT_SORT!
|
||||
$assetID: ID!
|
||||
) {
|
||||
asset(id: $assetID) {
|
||||
...StreamContainer_asset
|
||||
@arguments(count: $count, cursor: $cursor, orderBy: $orderBy)
|
||||
}
|
||||
}
|
||||
`,
|
||||
}
|
||||
)(StreamContainer);
|
||||
|
||||
export type StreamContainerProps = PropTypesOf<typeof enhanced>;
|
||||
|
||||
@@ -232,7 +232,10 @@ type PageInfo {
|
||||
"""
|
||||
Indicates that there are more nodes after this subset.
|
||||
"""
|
||||
hasNextPage: Boolean!
|
||||
hasNextPage: Boolean
|
||||
hasPreviousPage: Boolean
|
||||
startCursor: Cursor
|
||||
endCursor: Cursor
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
@@ -280,6 +280,7 @@ async function retrieveConnection(
|
||||
edges,
|
||||
pageInfo: {
|
||||
hasNextPage,
|
||||
endCursor: (nodes.length && nodes[nodes.length - 1].created_at) || null,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface Edge<T> {
|
||||
|
||||
export interface PageInfo {
|
||||
hasNextPage: boolean;
|
||||
endCursor: Cursor;
|
||||
}
|
||||
|
||||
export interface Connection<T> {
|
||||
|
||||
Reference in New Issue
Block a user