From b1580d753f403d4b088cf4579d29c1976da20605 Mon Sep 17 00:00:00 2001 From: xaralis <filip.varecha@fragaria.cz> Date: Thu, 7 Jan 2021 11:24:41 +0100 Subject: [PATCH] feat: drop traditional pagination in favor of auto window expansion --- .env | 2 +- src/actions/posts.js | 2 - src/components/onboarding/index.jsx | 17 +---- src/components/posts/Post.jsx | 14 ++-- src/components/posts/PostList.jsx | 100 +++++++++++++++++----------- src/containers/PostFilters.jsx | 51 +------------- src/containers/PostsContainer.jsx | 10 +-- src/pages/Home.jsx | 4 +- src/stores.js | 2 - typings/cf2021.d.ts | 2 - 10 files changed, 79 insertions(+), 125 deletions(-) diff --git a/.env b/.env index 2283bc6..613ffb1 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ -REACT_APP_STYLEGUIDE_URL=https://styleguide.pir-test.eu/2.3.0 +REACT_APP_STYLEGUIDE_URL=https://styleguide.pir-test.eu/2.3.1 REACT_APP_API_BASE_URL=https://cf2021.pir-test.eu/api REACT_APP_MATOMO_ID=135 REACT_APP_SENTRY_DSN=https://aa80453ff4d54b9a9c1b49e79060498a@sentry.pir-test.eu/14 diff --git a/src/actions/posts.js b/src/actions/posts.js index eab9e29..238fdf2 100644 --- a/src/actions/posts.js +++ b/src/actions/posts.js @@ -36,8 +36,6 @@ export const loadPosts = createAsyncAction( state.window = { items: filteredPosts.map(property("id")), itemCount: filteredPosts.length, - page: 1, - perPage: 20, }; }); } diff --git a/src/components/onboarding/index.jsx b/src/components/onboarding/index.jsx index 6f60ffd..44462d5 100644 --- a/src/components/onboarding/index.jsx +++ b/src/components/onboarding/index.jsx @@ -113,7 +113,7 @@ export const steps = [ </div> </> ), - placement: "right", + placement: "center", }, { target: ".joyride-filters", @@ -132,21 +132,6 @@ export const steps = [ ), placement: "bottom", }, - { - target: ".joyride-pagination", - content: ( - <> - <h1 className="head-alt-sm mb-4">StránkovánĂ pĹ™ĂspÄ›vkĹŻ</h1> - <div className="leading-snug text-base space-y-2"> - <p> - PĹ™ĂspÄ›vky jsou stránkovanĂ© <strong>po dvaceti</strong>. Stránky - mĹŻĹľeš pĹ™epĂnat tÄ›mito šipkami. - </p> - </div> - </> - ), - placement: "bottom", - }, { target: ".joyride-announcements", content: ( diff --git a/src/components/posts/Post.jsx b/src/components/posts/Post.jsx index 763e221..f6262e8 100644 --- a/src/components/posts/Post.jsx +++ b/src/components/posts/Post.jsx @@ -23,6 +23,7 @@ const Post = ({ currentUser, supportThreshold, canThumb, + reportSeen = true, onLike, onDislike, onHide, @@ -36,20 +37,21 @@ const Post = ({ onEdit, onArchive, onSeen, + ...props }) => { const { ref, inView } = useInView({ threshold: 0.8, trackVisibility: true, - delay: 1500, - skip: seen, + delay: 1000, + skip: !reportSeen, triggerOnce: true, }); useEffect(() => { - if (!seen && inView && onSeen) { + if (inView && onSeen) { onSeen(); } - }); + }, [inView, onSeen]); const wrapperClassName = classNames( "flex items-start p-4 lg:p-2 lg:py-3 lg:-mx-2 transition duration-500", @@ -193,7 +195,7 @@ const Post = ({ const thumbsVisible = !archived && (type === "post" || state === "announced"); return ( - <div className={wrapperClassName} ref={ref}> + <div className={wrapperClassName} ref={ref} {...props}> <img src={`https://a.pirati.cz/piratar/200/${author.username}.jpg`} className="w-8 h-8 lg:w-14 lg:h-14 mr-3 rounded object-cover" @@ -327,7 +329,7 @@ const Post = ({ </div> <div className="overflow-hidden"> <div - className="text-sm lg:text-base text-black leading-normal content-block overflow-x-scroll" + className="text-sm lg:text-base text-black leading-normal content-block overflow-x-scroll overflow-y-hidden" dangerouslySetInnerHTML={htmlContent} ></div> </div> diff --git a/src/components/posts/PostList.jsx b/src/components/posts/PostList.jsx index 607cc01..e80a6fd 100644 --- a/src/components/posts/PostList.jsx +++ b/src/components/posts/PostList.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; import classNames from "classnames"; import Post from "./Post"; @@ -30,6 +30,9 @@ const PostList = ({ responderFn(post); }; + const windowSize = 20; + const [window, setWindow] = useState(windowSize); + const onPostLike = buildHandler(onLike); const onPostDislike = buildHandler(onDislike); const onPostEdit = buildHandler(onEdit); @@ -47,48 +50,65 @@ const PostList = ({ onRejectProcedureProposalByChairman ); - const onPostSeen = (post) => () => { - onSeen(post); - }; + // Reset window when items change (possibly due to filtering). + useEffect(() => { + setWindow(windowSize); + }, [items, setWindow]); + + const onPostSeen = useCallback( + (post) => () => { + if (!post.seen) { + onSeen(post); + } + + // Once last post in window is reached, attempt show more. + if (items.indexOf(post) === window - 1) { + setWindow(window + windowSize); + } + }, + [items, onSeen, window] + ); + + const windowItems = useMemo(() => { + return items.slice(0, window).filter((item) => !item.hidden); + }, [items, window]); return ( <div className={classNames("space-y-px", className)}> - {items - .filter((item) => !item.hidden) - .map((item) => ( - <Post - key={item.id} - datetime={item.datetime} - author={item.author} - type={item.type} - state={item.state} - content={item.contentHtml} - ranking={item.ranking} - historyLog={item.historyLog} - modified={item.modified} - seen={item.seen} - archived={item.archived} - dimIfArchived={dimArchived} - currentUser={currentUser} - supportThreshold={supportThreshold} - canThumb={canThumb} - onLike={onPostLike(item)} - onDislike={onPostDislike(item)} - onHide={onPostHide(item)} - onBanUser={onPostBanUser(item)} - onUnbanUser={onPostUnbanUser(item)} - onInviteUser={onPostInviteUser(item)} - onAnnounceProcedureProposal={onPostAnnounceProcedureProposal(item)} - onAcceptProcedureProposal={onPostAcceptProcedureProposal(item)} - onRejectProcedureProposal={onPostRejectProcedureProposal(item)} - onRejectProcedureProposalByChairman={onPostRejectProcedureProposalByChairman( - item - )} - onEdit={onPostEdit(item)} - onArchive={onPostArchive(item)} - onSeen={onPostSeen(item)} - /> - ))} + {windowItems.map((item, idx) => ( + <Post + key={item.id} + datetime={item.datetime} + author={item.author} + type={item.type} + state={item.state} + content={item.contentHtml} + ranking={item.ranking} + modified={item.modified} + seen={item.seen} + reportSeen={!item.seen || idx === window - 1} + archived={item.archived} + dimIfArchived={dimArchived} + currentUser={currentUser} + supportThreshold={supportThreshold} + canThumb={canThumb} + onLike={onPostLike(item)} + onDislike={onPostDislike(item)} + onHide={onPostHide(item)} + onBanUser={onPostBanUser(item)} + onUnbanUser={onPostUnbanUser(item)} + onInviteUser={onPostInviteUser(item)} + onAnnounceProcedureProposal={onPostAnnounceProcedureProposal(item)} + onAcceptProcedureProposal={onPostAcceptProcedureProposal(item)} + onRejectProcedureProposal={onPostRejectProcedureProposal(item)} + onRejectProcedureProposalByChairman={onPostRejectProcedureProposalByChairman( + item + )} + onEdit={onPostEdit(item)} + onArchive={onPostArchive(item)} + onSeen={onPostSeen(item)} + /> + ))} {showAddPostCta && !items.length && ( <p className="p-4 lg:p-0 lg:py-3 leading-snug text-sm md:text-base"> Nikdo zatĂm žádnĂ˝ odpovĂdajĂcĂ pĹ™ĂspÄ›vek do rozpravy nepĹ™idal. Budeš diff --git a/src/containers/PostFilters.jsx b/src/containers/PostFilters.jsx index e1ccec4..5ade1dd 100644 --- a/src/containers/PostFilters.jsx +++ b/src/containers/PostFilters.jsx @@ -1,15 +1,11 @@ -import React, { useCallback } from "react"; -import pick from "lodash/pick"; +import React from "react"; -import Chip from "components/Chip"; import Dropdown from "components/Dropdown"; import { PostStore } from "stores"; import { updateWindowPosts } from "utils"; const PostFilters = () => { - const { window, filters } = PostStore.useState((state) => - pick(state, ["window", "filters", "items"]) - ); + const filters = PostStore.useState((state) => state.filters); const flagsOptions = [ { title: "Vše", value: "all" }, @@ -25,8 +21,6 @@ const PostFilters = () => { { title: "Jen návrhy", value: "proposalsOnly" }, { title: "Jen pĹ™ĂspÄ›vky", value: "discussionOnly" }, ]; - const hasNextPage = window.page * window.perPage < window.itemCount; - const hasPrevPage = window.page > 1; const setFilter = (prop, newValue, resetPage = true) => { PostStore.update((state) => { @@ -45,24 +39,6 @@ const PostFilters = () => { const onSortChange = (newValue) => setFilter("sort", newValue, false); const onTypeChange = (newValue) => setFilter("type", newValue); - const onNextPage = useCallback(() => { - if (hasNextPage) { - PostStore.update((state) => { - state.window.page = state.window.page + 1; - }); - } - }, [hasNextPage]); - const onPrevPage = useCallback(() => { - if (hasPrevPage) { - PostStore.update((state) => { - state.window.page = state.window.page - 1; - }); - } - }, [hasPrevPage]); - - const enabledPaginatorClass = "cursor-pointer text-xs"; - const disabledPaginatorClass = "opacity-25 cursor-not-allowed text-xs"; - return ( <div className="flex flex-col space-y-2 xl:space-y-0 xl:space-x-8 xl:flex-row xl:items-center"> <div className="-mx-1 joyride-filters"> @@ -85,29 +61,6 @@ const PostFilters = () => { className="text-xs ml-1 mt-2 xl:mt-0" /> </div> - - <div className="joyride-pagination"> - <Chip - color="grey-125" - className={ - hasPrevPage ? enabledPaginatorClass : disabledPaginatorClass - } - hoveractive - onClick={onPrevPage} - > - <span className="ico--chevron-left"></span> - </Chip> - <Chip - color="grey-125" - className={ - hasNextPage ? enabledPaginatorClass : disabledPaginatorClass - } - hoveractive - onClick={onNextPage} - > - <span className="ico--chevron-right"></span> - </Chip> - </div> </div> ); }; diff --git a/src/containers/PostsContainer.jsx b/src/containers/PostsContainer.jsx index 3fd62b1..500f083 100644 --- a/src/containers/PostsContainer.jsx +++ b/src/containers/PostsContainer.jsx @@ -1,4 +1,4 @@ -import React, { useCallback } from "react"; +import React, { useCallback, useMemo } from "react"; import pick from "lodash/pick"; import { @@ -191,9 +191,9 @@ const PostsContainer = ({ className, showAddPostCta }) => { [setUserToInvite] ); - const sliceStart = (window.page - 1) * window.perPage; - const sliceEnd = window.page * window.perPage; - const windowItems = window.items.map((postId) => items[postId]); + const windowItems = useMemo(() => { + return window.items.map((postId) => items[postId]); + }, [items, window.items]); return ( <> @@ -203,7 +203,7 @@ const PostsContainer = ({ className, showAddPostCta }) => { </ErrorMessage> )} <PostList - items={windowItems.slice(sliceStart, sliceEnd)} + items={windowItems} showAddPostCta={showAddPostCta} canThumb={isAuthenticated} onLike={like.run} diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index fdf8f2c..8918b1e 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -213,10 +213,10 @@ const Home = () => { <div className="pl-4 pt-1 lg:pt-5"> <div className="space-x-4 inline-flex items-center"> <button - className="ico--question text-grey-200 hover:text-black text-lg" + className="ico--question text-grey-200 hidden lg:block hover:text-black text-lg" aria-label="PotĹ™ebuješ pomoc? SpusĹĄ si znovu nápovÄ›du jak tuhle aplikaci pouĹľĂvat." data-tip="PotĹ™ebuješ pomoc? SpusĹĄ si znovu nápovÄ›du jak tuhle aplikaci pouĹľĂvat." - data-tip-at="left" + data-tip-at="top" onClick={showTutorial} /> {displayActions && ( diff --git a/src/stores.js b/src/stores.js index e98a90e..6be388b 100644 --- a/src/stores.js +++ b/src/stores.js @@ -48,8 +48,6 @@ const postStoreInitial = { window: { items: [], itemCount: 0, - page: 1, - perPage: 20, }, filters: { flags: "active", diff --git a/typings/cf2021.d.ts b/typings/cf2021.d.ts index e73fbbf..b699e18 100644 --- a/typings/cf2021.d.ts +++ b/typings/cf2021.d.ts @@ -158,8 +158,6 @@ declare namespace CF2021 { window: { items: string[]; itemCount: number; - page: number; - perPage: number; }; filters: PostStoreFilters; } -- GitLab