Track seen contracts in feed. Order feed by seen time priority.

This commit is contained in:
James Grugett 2022-03-17 02:29:08 -05:00
parent 3b933923e2
commit addc883440
6 changed files with 115 additions and 8 deletions

View File

@ -1,5 +1,5 @@
// From https://tailwindui.com/components/application-ui/lists/feeds // From https://tailwindui.com/components/application-ui/lists/feeds
import { Fragment, useState } from 'react' import { Fragment, useRef, useState } from 'react'
import * as _ from 'lodash' import * as _ from 'lodash'
import { import {
BanIcon, BanIcon,
@ -44,10 +44,9 @@ import { Answer } from '../../../common/answer'
import { ActivityItem } from './activity-items' import { ActivityItem } from './activity-items'
import { FreeResponse, FullContract } from '../../../common/contract' import { FreeResponse, FullContract } from '../../../common/contract'
import { BuyButton } from '../yes-no-selector' import { BuyButton } from '../yes-no-selector'
import { AnswerItem } from '../answers/answer-item'
import { getDpmOutcomeProbability } from '../../../common/calculate-dpm' import { getDpmOutcomeProbability } from '../../../common/calculate-dpm'
import { BetPanel } from '../bet-panel'
import { AnswerBetPanel } from '../answers/answer-bet-panel' import { AnswerBetPanel } from '../answers/answer-bet-panel'
import { useSaveSeenContract } from '../../hooks/use-seen-contracts'
export function FeedItems(props: { export function FeedItems(props: {
contract: Contract contract: Contract
@ -57,8 +56,11 @@ export function FeedItems(props: {
const { contract, items, betRowClassName } = props const { contract, items, betRowClassName } = props
const { outcomeType } = contract const { outcomeType } = contract
const ref = useRef<HTMLDivElement | null>(null)
useSaveSeenContract(ref, contract)
return ( return (
<div className="flow-root pr-2 md:pr-0"> <div className="flow-root pr-2 md:pr-0" ref={ref}>
<div className={clsx(tradingAllowed(contract) ? '' : '-mb-6')}> <div className={clsx(tradingAllowed(contract) ? '' : '-mb-6')}>
{items.map((item, activityItemIdx) => ( {items.map((item, activityItemIdx) => (
<div key={item.id} className="relative pb-6"> <div key={item.id} className="relative pb-6">

View File

@ -24,7 +24,8 @@ function lastActivityTime(contract: Contract) {
export function findActiveContracts( export function findActiveContracts(
allContracts: Contract[], allContracts: Contract[],
recentComments: Comment[], recentComments: Comment[],
recentBets: Bet[] recentBets: Bet[],
seenContracts: { [contractId: string]: number }
) { ) {
const idToActivityTime = new Map<string, number>() const idToActivityTime = new Map<string, number>()
function record(contractId: string, time: number) { function record(contractId: string, time: number) {
@ -64,5 +65,34 @@ export function findActiveContracts(
activeContracts, activeContracts,
(c) => -(idToActivityTime.get(c.id) ?? 0) (c) => -(idToActivityTime.get(c.id) ?? 0)
) )
return activeContracts.slice(0, MAX_ACTIVE_CONTRACTS)
const contractComments = _.groupBy(
recentComments,
(comment) => comment.contractId
)
const contractMostRecentComment = _.mapValues(
contractComments,
(comments) => _.maxBy(comments, (c) => c.createdTime) as Comment
)
const prioritizedContracts = _.sortBy(activeContracts, (c) => {
const seenTime = seenContracts[c.id]
if (!seenTime) {
return 0
}
const lastCommentTime = contractMostRecentComment[c.id]?.createdTime
if (lastCommentTime && lastCommentTime > seenTime) {
return 1
}
const lastBetTime = contractMostRecentBet[c.id]?.createdTime
if (lastBetTime && lastBetTime > seenTime) {
return 2
}
return seenTime
})
return prioritizedContracts.slice(0, MAX_ACTIVE_CONTRACTS)
} }

View File

@ -11,6 +11,7 @@ import { Contract, getActiveContracts } from '../lib/firebase/contracts'
import { listAllFolds } from '../lib/firebase/folds' import { listAllFolds } from '../lib/firebase/folds'
import { useInactiveContracts } from './use-contracts' import { useInactiveContracts } from './use-contracts'
import { useFollowedFolds } from './use-fold' import { useFollowedFolds } from './use-fold'
import { useSeenContracts } from './use-seen-contracts'
import { useUserBetContracts } from './use-user-bets' import { useUserBetContracts } from './use-user-bets'
// used in static props // used in static props
@ -95,10 +96,13 @@ export const useFindActiveContracts = (props: {
}) => { }) => {
const { contracts, recentBets, recentComments } = props const { contracts, recentBets, recentComments } = props
const seenContracts = useSeenContracts()
const activeContracts = findActiveContracts( const activeContracts = findActiveContracts(
contracts, contracts,
recentComments, recentComments,
recentBets recentBets,
seenContracts
) )
const betsByContract = _.groupBy(recentBets, (bet) => bet.contractId) const betsByContract = _.groupBy(recentBets, (bet) => bet.contractId)

View File

@ -0,0 +1,29 @@
import { RefObject, useEffect, useState } from 'react'
export function useIsVisible(elementRef: RefObject<Element>) {
return !!useIntersectionObserver(elementRef)?.isIntersecting
}
function useIntersectionObserver(
elementRef: RefObject<Element>
): IntersectionObserverEntry | undefined {
const [entry, setEntry] = useState<IntersectionObserverEntry>()
const updateEntry = ([entry]: IntersectionObserverEntry[]): void => {
setEntry(entry)
}
useEffect(() => {
const node = elementRef?.current
const hasIOSupport = !!window.IntersectionObserver
if (!hasIOSupport || !node) return
const observer = new IntersectionObserver(updateEntry, {})
observer.observe(node)
return () => observer.disconnect()
}, [elementRef])
return entry
}

View File

@ -0,0 +1,42 @@
import _ from 'lodash'
import { useEffect, RefObject, useState } from 'react'
import { Contract } from '../../common/contract'
import { useIsVisible } from './use-is-visible'
export const useSeenContracts = () => {
const [seenContracts, setSeenContracts] = useState<{
[contractId: string]: number
}>({})
useEffect(() => {
setSeenContracts(getSeenContracts())
}, [])
return seenContracts
}
export const useSaveSeenContract = (
ref: RefObject<Element>,
contract: Contract
) => {
const isVisible = useIsVisible(ref)
useEffect(() => {
if (isVisible) {
const newSeenContracts = {
...getSeenContracts(),
[contract.id]: Date.now(),
}
localStorage.setItem(key, JSON.stringify(newSeenContracts))
}
}, [isVisible, contract])
}
const key = 'feed-seen-contracts'
const getSeenContracts = () => {
return _.mapValues(
JSON.parse(localStorage.getItem(key) ?? '{}'),
(time) => +time
)
}

View File

@ -55,7 +55,7 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) {
contracts.map((contract) => listAllBets(contract.id)) contracts.map((contract) => listAllBets(contract.id))
) )
let activeContracts = findActiveContracts(contracts, [], _.flatten(bets)) let activeContracts = findActiveContracts(contracts, [], _.flatten(bets), {})
const [resolved, unresolved] = _.partition( const [resolved, unresolved] = _.partition(
activeContracts, activeContracts,
({ isResolved }) => isResolved ({ isResolved }) => isResolved