From 4d458734ca2f14bbf08b85152953d6292dd991b7 Mon Sep 17 00:00:00 2001 From: natjms Date: Sat, 13 Mar 2021 07:24:51 -0400 Subject: [PATCH] Create ViewComments page to display post context --- assets/eva-icons/paper-plane.png | Bin 0 -> 10138 bytes src/App.js | 7 +- src/components/navigation/navigators.js | 2 +- src/components/pages/feed.js | 4 +- src/components/pages/view-comments.js | 361 ++++++++++++++++++++++++ src/components/pages/view-post.js | 6 +- src/components/posts/paged-grid.js | 2 +- src/components/posts/post.js | 56 +--- src/components/posts/timeline-view.js | 6 +- src/interface/rendering.js | 37 +++ 10 files changed, 430 insertions(+), 51 deletions(-) create mode 100644 assets/eva-icons/paper-plane.png create mode 100644 src/components/pages/view-comments.js create mode 100644 src/interface/rendering.js diff --git a/assets/eva-icons/paper-plane.png b/assets/eva-icons/paper-plane.png new file mode 100644 index 0000000000000000000000000000000000000000..1432e161d8252a67bd37d8b9b81c089d120433ab GIT binary patch literal 10138 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 literal 0 HcmV?d00001 diff --git a/src/App.js b/src/App.js index 5a25377..9b7d6db 100644 --- a/src/App.js +++ b/src/App.js @@ -6,9 +6,11 @@ import { createStackNavigator } from "react-navigation-stack"; import { registerRootComponent } from 'expo'; +import ViewPostJsx from "src/components/pages/view-post"; +import ViewCommentsJsx from "src/components/pages/view-comments.js"; + import FeedJsx from "src/components/pages/feed"; import ProfileJsx, { ViewProfileJsx } from "src/components/pages/profile"; -import ViewPostJsx from "src/components/pages/view-post"; import DiscoverJsx from 'src/components/pages/discover'; import SearchJsx from 'src/components/pages/discover/search'; import ViewHashtagJsx from 'src/components/pages/discover/view-hashtag'; @@ -21,9 +23,10 @@ const Stack = createStackNavigator({ Profile: { screen: ProfileJsx, }, Search: { screen: SearchJsx }, ViewPost: { screen: ViewPostJsx }, + ViewComments: { screen: ViewCommentsJsx }, ViewProfile: { screen: ViewProfileJsx }, ViewHashtag: { screen: ViewHashtagJsx } -}, { +}, { initialRouteKey: "Feed", headerMode: "none", navigationOptions: { diff --git a/src/components/navigation/navigators.js b/src/components/navigation/navigators.js index f1629d2..d3776c1 100644 --- a/src/components/navigation/navigators.js +++ b/src/components/navigation/navigators.js @@ -9,7 +9,7 @@ import TrayJsx from "src/components/navigation/tray"; // Provider for context menus // Allows for establishing global styling of context menus -const ContextJsx = (props) => { +export const ContextJsx = (props) => { return ( { props.children } diff --git a/src/components/pages/feed.js b/src/components/pages/feed.js index b2ee92d..ac9b3e0 100644 --- a/src/components/pages/feed.js +++ b/src/components/pages/feed.js @@ -43,7 +43,9 @@ const FeedJsx = (props) => { - +
chunkWhile([1,1,1,2,2], (a, b) => a == b) + * [[1,1,1], [2,2]] + */ + + let parts; + + if (arr == []) { + return [] + } else { + parts = [[arr[0]]]; + } + + let tail = arr.slice(1); + + if (tail == []) { + return parts; + } + + for (let i = 0; i < tail.length; i++) { + let lastPart = parts[parts.length - 1]; + if (fun(tail[i], lastPart[lastPart.length - 1])) { + // If fun returns something truthy, push tail[i] to the end of the + // partition at the end of the new array. + parts[parts.length - 1].push(tail[i]) + } else { + // Create a new partition starting with tail[i] + parts.push([tail[i]]) + } + } + + return parts; +} + +function threadify(descendants, parentID) { + /* + * Take a list of descendants and sort them into a 2D matrix. + * The first item is the direct descendant of parentID post and the rest + * are all the descendants of the direct descendant in order of id, the + * way Instagram displays conversations in comments. + * i.e. [[first level comment, ...descendants]] + */ + + if (descendants == []) { + return []; + } + + // Sort comments in order of increasing reply id + const comments = descendants.sort((first, second) => { + return first.in_reply_to_id - second.in_reply_to_id; + }); + + // Return partitions of comments based on their reply id + const byReply = chunkWhile(comments, (a, b) => { + return a.in_reply_to_id == b.in_reply_to_id; + }); + + // Start with just the first level comments. + // All these elements should be in singleton arrays so they can be + // appended to. + let sorted = byReply[0].map(x => [x]); + + let sub = byReply.slice(1); // All sub-comments + + // Repeate the procedure until sub is empty (i.e all comments have been + // sorted) + while (sub.length > 0) { + sorted.forEach((thread, threadIndex) => { + for (let i = 0; i < thread.length; i++) { + const id = thread[i].id; + + // Search for comment groups with that id + for(let subIndex = 0; subIndex < sub.length; subIndex++) { + // All items in each partition should have the same reply id + if(id == sub[subIndex][0].in_reply_to_id) { + // Move the newly found thread contents to thread in + // sorted + sorted[threadIndex] = sorted[threadIndex].concat(sub[subIndex]); + sub.splice(subIndex, 1); + } + } + } + }); +} + +return sorted; +} + +const CommentJsx = (props) => { + const packs = { + favourited: { + active: require("assets/eva-icons/post-actions/heart-active.png"), + inactive: require("assets/eva-icons/post-actions/heart-inactive.png") + } + }; + + return ( + + + + + { props.data.username }  + { props.data.content } + + + + + { timeToAge((new Date()).getTime(), props.data.created_at) } + + + + + + Reply + + + + + + + + + + ); +} + +const ViewCommentsJsx = (props) => { + let [state, setState] = useState({ + postData: undefined, + loaded: false, + reply: "" + }); + + useEffect(() => { + (() => { // Some magical function that will get all the data needed + setState({ ...state, + descendants: threadify(TEST_CONTEXT.descendants), + postData: props.navigation.getParam("postData"), + loaded: true, + }); + })(); + }, []); + + return ( + + + + + { state.loaded ? + + + + + + { + state.descendants.map((thread, i) => { + const comment = thread[0]; + const subs = thread.slice(1); + + return ( + + + { + subs.map((sub, j) => { + return ( + + + + ) + }) + } + + ); + }) + } + + + : + } + + + + setState({...state, reply: c }) }/> + + + + + + + + + ); +} + +const SCREEN_WIDTH = Dimensions.get("window").width; + +const styles = { + bold: { + fontWeight: "bold", + }, + container: { + display: "flex", + flexDirection: "row", + flexShrink: 1, + marginTop: 10, + marginBottom: 10, + marginRight: 20, + }, + avatar: { + marginLeft: 20, + marginRight: 20, + width: 50, + height: 50, + borderRadius: "100%" + }, + contentContainer: { + flexShrink: 1 + }, + parentPost: { + borderBottomWidth: 1, + borderBottomColor: "#CCC", + marginBottom: 10 + }, + sub: { + marginLeft: SCREEN_WIDTH / 8 + }, + commentActions: { + display: "flex", + flexDirection: "row", + alignItems: "center", + }, + actionText: { + fontSize: 13, + color: "#666", + paddingRight: 10 + }, + heart: { + width: 15, + height: 15 + }, + + commentForm: { + flexDirection: "row", + alignItems: "center", + backgroundColor: "white", + + borderTopWidth: 1, + borderTopColor: "#CCC", + + paddingTop: 10, + paddingBottom: 10 + }, + commentInput: { + borderWidth: 0, + padding: 10, + flexGrow: 3, + marginRight: 20 + }, + submitContainer: { + marginLeft: "auto", + marginRight: 20 + }, + commentSubmit: { + width: 30, + height: 30, + } +}; + +export default ViewCommentsJsx; diff --git a/src/components/pages/view-post.js b/src/components/pages/view-post.js index 106fcd7..5cc87ce 100644 --- a/src/components/pages/view-post.js +++ b/src/components/pages/view-post.js @@ -8,9 +8,11 @@ const ViewPostJsx = (props) => { - + ); } -export default ViewPostJsx; \ No newline at end of file +export default ViewPostJsx; diff --git a/src/components/posts/paged-grid.js b/src/components/posts/paged-grid.js index 25a0542..50e964c 100644 --- a/src/components/posts/paged-grid.js +++ b/src/components/posts/paged-grid.js @@ -89,4 +89,4 @@ const styles = { } } -export default PagedGridJSX; \ No newline at end of file +export default PagedGridJSX; diff --git a/src/components/posts/post.js b/src/components/posts/post.js index 5fc6e48..2a9905f 100644 --- a/src/components/posts/post.js +++ b/src/components/posts/post.js @@ -9,6 +9,8 @@ import { renderers } from "react-native-popup-menu"; +import { pluralize, timeToAge } from "src/interface/rendering" + import PostActionBarJsx from "src/components/posts/post-action-bar"; const SCREEN_WIDTH = Dimensions.get("window").width; @@ -18,14 +20,6 @@ const TEST_IMAGE = "https://cache.desktopnexus.com/thumbseg/2255/2255124-bigthum // This will be used in RawPostJsx const { SlideInMenu } = renderers; -function pluralize(n, singular, plural) { - if (n < 2) { - return singular; - } else { - return plural; - } -} - function getAutoHeight(w1, h1, w2) { /* Given the original dimensions and the new width, calculate what would @@ -45,36 +39,6 @@ function getAutoHeight(w1, h1, w2) { return w2 * (h1 / w1) } -function timeToAge(time1, time2) { - /* - Output a friendly string to describe the age of a post, where `time1` and - `time2` are in milliseconds - */ - - const between = (n, lower, upper) => n >= lower && n < upper; - - const diff = time1 - time2; - - if (diff < 60000) { - return "Seconds ago" - } else if (between(diff, 60000, 3600000)) { - const nMin = Math.floor(diff / 60000); - return nMin + " " + pluralize(nMin, "minute", "minutes") + " ago"; - } else if (between(diff, 3600000, 86400000)) { - const nHours = Math.floor(diff / 3600000); - return nHours + " " + pluralize(nHours, "hour", "hours") + " ago"; - } else if (between(diff, 86400000, 2629800000)) { - const nDays = Math.floor(diff / 86400000); - return nDays + " " + pluralize(nDays, "day", "days") + " ago"; - } else if (between(diff, 2629800000, 31557600000)) { - const nMonths = Math.floor(diff / 2629800000); - return nMonths + " " + pluralize(nMonths, "month", "months") + " ago"; - } else { - const nYears = Math.floor(diff / 31557600000); - return nYears + " " + pluralize(nYears, "year", "years") + " ago"; - } -} - export const RawPostJsx = (props) => { const repliesCount = props.data.replies_count; @@ -125,7 +89,13 @@ export const RawPostJsx = (props) => { { props.data.username } { props.data.content } - + props.navigation.navigate("ViewComments", { + originTab: props.navigation.getParam("originTab"), + postData: props.data + }) + }> { commentsText } @@ -169,7 +139,8 @@ export const PostByDataJsx = (props) => { + height = { state.height } + navigation = { props.navigation }/> : } ); @@ -210,7 +181,7 @@ export const PostByIdJsx = (props) => { }); }); })(); - }); + }, []); return ( @@ -218,7 +189,8 @@ export const PostByIdJsx = (props) => { + height = { state.height } + navigation = { props.navigation }/> : } diff --git a/src/components/posts/timeline-view.js b/src/components/posts/timeline-view.js index a66736b..70a158f 100644 --- a/src/components/posts/timeline-view.js +++ b/src/components/posts/timeline-view.js @@ -9,7 +9,9 @@ const TimelineViewJsx = (props) => { { props.posts.map((post, i) => { return ( - + ); }) } @@ -17,4 +19,4 @@ const TimelineViewJsx = (props) => { ); }; -export default TimelineViewJsx; \ No newline at end of file +export default TimelineViewJsx; diff --git a/src/interface/rendering.js b/src/interface/rendering.js new file mode 100644 index 0000000..6dee69e --- /dev/null +++ b/src/interface/rendering.js @@ -0,0 +1,37 @@ +export function pluralize(n, singular, plural) { + if (n < 2) { + return singular; + } else { + return plural; + } +} + +export function timeToAge(time1, time2) { + /* + Output a friendly string to describe the age of a post, where `time1` and + `time2` are in milliseconds + */ + + const between = (n, lower, upper) => n >= lower && n < upper; + + const diff = time1 - time2; + + if (diff < 60000) { + return "Seconds ago" + } else if (between(diff, 60000, 3600000)) { + const nMin = Math.floor(diff / 60000); + return nMin + " " + pluralize(nMin, "minute", "minutes") + " ago"; + } else if (between(diff, 3600000, 86400000)) { + const nHours = Math.floor(diff / 3600000); + return nHours + " " + pluralize(nHours, "hour", "hours") + " ago"; + } else if (between(diff, 86400000, 2629800000)) { + const nDays = Math.floor(diff / 86400000); + return nDays + " " + pluralize(nDays, "day", "days") + " ago"; + } else if (between(diff, 2629800000, 31557600000)) { + const nMonths = Math.floor(diff / 2629800000); + return nMonths + " " + pluralize(nMonths, "month", "months") + " ago"; + } else { + const nYears = Math.floor(diff / 31557600000); + return nYears + " " + pluralize(nYears, "year", "years") + " ago"; + } +}