live comments: favicon (#2400)
* live comments: stable navigator for new outlined comments * favicons: FaviconProvider, handle new comments favicon via navigator * navigator keyboard shortcuts: arrow right/escape key * enhance: responsive fixed positioning; cleanup enhance: - two types of padding for desktop and mobile via CSS cleanup: - use appropriate <aside> for navigator - reorder CSS * Comments Navigator Context, new comments dot UI, refs autosorting, auto-untrack children - Navigator Context for item pages UI/UX - WIP: compact comments dot UI on navbars - long press to clear tracked refs - auto-untrack node's children on scroll Logic - auto-sort comment refs via createdAt - remove outline on untrack if called by scroll * stable navigator dot UI positioning * cleanup: better naming, clear structure * re-instate favicon state updates on navigator * CSS visibility tweaks * scroll to start position of ref * fix undefined navigator on other comment calls * add explanation for early favicon clear --------- Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
This commit is contained in:
parent
df2ccd9840
commit
610e6dcb91
48
components/favicon.js
Normal file
48
components/favicon.js
Normal file
@ -0,0 +1,48 @@
|
||||
import { createContext, useContext, useMemo, useState } from 'react'
|
||||
import { useHasNewNotes } from './use-has-new-notes'
|
||||
import Head from 'next/head'
|
||||
|
||||
const FAVICONS = {
|
||||
default: '/favicon.png',
|
||||
notify: '/favicon-notify.png',
|
||||
comments: '/favicon-comments.png',
|
||||
notifyWithComments: '/favicon-notify-with-comments.png'
|
||||
}
|
||||
|
||||
const getFavicon = (hasNewNotes, hasNewComments) => {
|
||||
if (hasNewNotes && hasNewComments) return FAVICONS.notifyWithComments
|
||||
if (hasNewNotes) return FAVICONS.notify
|
||||
if (hasNewComments) return FAVICONS.comments
|
||||
return FAVICONS.default
|
||||
}
|
||||
|
||||
export const FaviconContext = createContext()
|
||||
|
||||
export default function FaviconProvider ({ children }) {
|
||||
const hasNewNotes = useHasNewNotes()
|
||||
const [hasNewComments, setHasNewComments] = useState(false)
|
||||
|
||||
const favicon = useMemo(() =>
|
||||
getFavicon(hasNewNotes, hasNewComments),
|
||||
[hasNewNotes, hasNewComments])
|
||||
|
||||
const contextValue = useMemo(() => ({
|
||||
favicon,
|
||||
hasNewNotes,
|
||||
hasNewComments,
|
||||
setHasNewComments
|
||||
}), [favicon, hasNewNotes, hasNewComments, setHasNewComments])
|
||||
|
||||
return (
|
||||
<FaviconContext.Provider value={contextValue}>
|
||||
<Head>
|
||||
<link rel='shortcut icon' href={favicon} />
|
||||
</Head>
|
||||
{children}
|
||||
</FaviconContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useFavicon () {
|
||||
return useContext(FaviconContext)
|
||||
}
|
@ -23,7 +23,6 @@ import { useWalletIndicator } from '@/wallets/client/hooks'
|
||||
import SwitchAccountList, { nextAccount, useAccounts } from '@/components/account'
|
||||
import { useShowModal } from '@/components/modal'
|
||||
import { numWithUnits } from '@/lib/format'
|
||||
import Head from 'next/head'
|
||||
|
||||
export function Brand ({ className }) {
|
||||
return (
|
||||
@ -121,9 +120,6 @@ export function NavNotifications ({ className }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<link rel='shortcut icon' href={hasNewNotes ? '/favicon-notify.png' : '/favicon.png'} />
|
||||
</Head>
|
||||
<Link href='/notifications' passHref legacyBehavior>
|
||||
<Nav.Link eventKey='notifications' className={classNames('position-relative', className)}>
|
||||
<NoteIcon height={28} width={20} className='theme' />
|
||||
|
@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState, startTransition, createContex
|
||||
import styles from './comment.module.css'
|
||||
import { useRouter } from 'next/router'
|
||||
import LongPressable from './long-pressable'
|
||||
import { useFavicon } from './favicon'
|
||||
|
||||
const CommentsNavigatorContext = createContext({
|
||||
navigator: {
|
||||
@ -28,6 +29,7 @@ export function useCommentsNavigatorContext () {
|
||||
|
||||
export function useCommentsNavigator () {
|
||||
const router = useRouter()
|
||||
const { setHasNewComments } = useFavicon()
|
||||
const [commentCount, setCommentCount] = useState(0)
|
||||
// refs in ref to not re-render on tracking
|
||||
const commentRefs = useRef([])
|
||||
@ -52,10 +54,12 @@ export function useCommentsNavigator () {
|
||||
const clearCommentRefs = useCallback(() => {
|
||||
commentRefs.current = []
|
||||
startTransition?.(() => setCommentCount(0))
|
||||
setHasNewComments(false)
|
||||
}, [])
|
||||
|
||||
// track a new comment
|
||||
const trackNewComment = useCallback((commentRef, createdAt) => {
|
||||
setHasNewComments(true)
|
||||
try {
|
||||
window.requestAnimationFrame(() => {
|
||||
if (!commentRef?.current || !commentRef.current.isConnected) return
|
||||
@ -89,6 +93,9 @@ export function useCommentsNavigator () {
|
||||
|
||||
// remove a comment ref from the list
|
||||
const untrackNewComment = useCallback((commentRef, options = {}) => {
|
||||
// we just need to read a single comment to clear the favicon
|
||||
setHasNewComments(false)
|
||||
|
||||
const { includeDescendants = false, clearOutline = false } = options
|
||||
|
||||
const refNode = commentRef.current
|
||||
|
@ -20,6 +20,7 @@ import { ChainFeeProvider } from '@/components/chain-fee.js'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { HasNewNotesProvider } from '@/components/use-has-new-notes'
|
||||
import WalletsProvider from '@/wallets/client/context'
|
||||
import FaviconProvider from '@/components/favicon'
|
||||
|
||||
const PWAPrompt = dynamic(() => import('react-ios-pwa-prompt'), { ssr: false })
|
||||
|
||||
@ -121,24 +122,26 @@ export default function MyApp ({ Component, pageProps: { ...props } }) {
|
||||
<MeProvider me={me}>
|
||||
<WalletsProvider>
|
||||
<HasNewNotesProvider>
|
||||
<ServiceWorkerProvider>
|
||||
<PriceProvider price={price}>
|
||||
<AnimationProvider>
|
||||
<ToastProvider>
|
||||
<ShowModalProvider>
|
||||
<BlockHeightProvider blockHeight={blockHeight}>
|
||||
<ChainFeeProvider chainFee={chainFee}>
|
||||
<ErrorBoundary>
|
||||
<Component ssrData={ssrData} {...otherProps} />
|
||||
{!router?.query?.disablePrompt && <PWAPrompt copyBody='This website has app functionality. Add it to your home screen to use it in fullscreen and receive notifications. In Safari:' promptOnVisit={2} />}
|
||||
</ErrorBoundary>
|
||||
</ChainFeeProvider>
|
||||
</BlockHeightProvider>
|
||||
</ShowModalProvider>
|
||||
</ToastProvider>
|
||||
</AnimationProvider>
|
||||
</PriceProvider>
|
||||
</ServiceWorkerProvider>
|
||||
<FaviconProvider>
|
||||
<ServiceWorkerProvider>
|
||||
<PriceProvider price={price}>
|
||||
<AnimationProvider>
|
||||
<ToastProvider>
|
||||
<ShowModalProvider>
|
||||
<BlockHeightProvider blockHeight={blockHeight}>
|
||||
<ChainFeeProvider chainFee={chainFee}>
|
||||
<ErrorBoundary>
|
||||
<Component ssrData={ssrData} {...otherProps} />
|
||||
{!router?.query?.disablePrompt && <PWAPrompt copyBody='This website has app functionality. Add it to your home screen to use it in fullscreen and receive notifications. In Safari:' promptOnVisit={2} />}
|
||||
</ErrorBoundary>
|
||||
</ChainFeeProvider>
|
||||
</BlockHeightProvider>
|
||||
</ShowModalProvider>
|
||||
</ToastProvider>
|
||||
</AnimationProvider>
|
||||
</PriceProvider>
|
||||
</ServiceWorkerProvider>
|
||||
</FaviconProvider>
|
||||
</HasNewNotesProvider>
|
||||
</WalletsProvider>
|
||||
</MeProvider>
|
||||
|
BIN
public/favicon-comments.png
Normal file
BIN
public/favicon-comments.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.4 KiB |
BIN
public/favicon-notify-with-comments.png
Normal file
BIN
public/favicon-notify-with-comments.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.5 KiB |
Loading…
x
Reference in New Issue
Block a user