Track seen contracts in feed. Order feed by seen time priority.
This commit is contained in:
parent
3b933923e2
commit
addc883440
|
@ -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">
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
29
web/hooks/use-is-visible.ts
Normal file
29
web/hooks/use-is-visible.ts
Normal 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
|
||||||
|
}
|
42
web/hooks/use-seen-contracts.ts
Normal file
42
web/hooks/use-seen-contracts.ts
Normal 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
|
||||||
|
)
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user