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 SwitchAccountList, { nextAccount, useAccounts } from '@/components/account'
|
||||||
import { useShowModal } from '@/components/modal'
|
import { useShowModal } from '@/components/modal'
|
||||||
import { numWithUnits } from '@/lib/format'
|
import { numWithUnits } from '@/lib/format'
|
||||||
import Head from 'next/head'
|
|
||||||
|
|
||||||
export function Brand ({ className }) {
|
export function Brand ({ className }) {
|
||||||
return (
|
return (
|
||||||
@ -121,9 +120,6 @@ export function NavNotifications ({ className }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
|
||||||
<link rel='shortcut icon' href={hasNewNotes ? '/favicon-notify.png' : '/favicon.png'} />
|
|
||||||
</Head>
|
|
||||||
<Link href='/notifications' passHref legacyBehavior>
|
<Link href='/notifications' passHref legacyBehavior>
|
||||||
<Nav.Link eventKey='notifications' className={classNames('position-relative', className)}>
|
<Nav.Link eventKey='notifications' className={classNames('position-relative', className)}>
|
||||||
<NoteIcon height={28} width={20} className='theme' />
|
<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 styles from './comment.module.css'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import LongPressable from './long-pressable'
|
import LongPressable from './long-pressable'
|
||||||
|
import { useFavicon } from './favicon'
|
||||||
|
|
||||||
const CommentsNavigatorContext = createContext({
|
const CommentsNavigatorContext = createContext({
|
||||||
navigator: {
|
navigator: {
|
||||||
@ -28,6 +29,7 @@ export function useCommentsNavigatorContext () {
|
|||||||
|
|
||||||
export function useCommentsNavigator () {
|
export function useCommentsNavigator () {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { setHasNewComments } = useFavicon()
|
||||||
const [commentCount, setCommentCount] = useState(0)
|
const [commentCount, setCommentCount] = useState(0)
|
||||||
// refs in ref to not re-render on tracking
|
// refs in ref to not re-render on tracking
|
||||||
const commentRefs = useRef([])
|
const commentRefs = useRef([])
|
||||||
@ -52,10 +54,12 @@ export function useCommentsNavigator () {
|
|||||||
const clearCommentRefs = useCallback(() => {
|
const clearCommentRefs = useCallback(() => {
|
||||||
commentRefs.current = []
|
commentRefs.current = []
|
||||||
startTransition?.(() => setCommentCount(0))
|
startTransition?.(() => setCommentCount(0))
|
||||||
|
setHasNewComments(false)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// track a new comment
|
// track a new comment
|
||||||
const trackNewComment = useCallback((commentRef, createdAt) => {
|
const trackNewComment = useCallback((commentRef, createdAt) => {
|
||||||
|
setHasNewComments(true)
|
||||||
try {
|
try {
|
||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
if (!commentRef?.current || !commentRef.current.isConnected) return
|
if (!commentRef?.current || !commentRef.current.isConnected) return
|
||||||
@ -89,6 +93,9 @@ export function useCommentsNavigator () {
|
|||||||
|
|
||||||
// remove a comment ref from the list
|
// remove a comment ref from the list
|
||||||
const untrackNewComment = useCallback((commentRef, options = {}) => {
|
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 { includeDescendants = false, clearOutline = false } = options
|
||||||
|
|
||||||
const refNode = commentRef.current
|
const refNode = commentRef.current
|
||||||
|
@ -20,6 +20,7 @@ import { ChainFeeProvider } from '@/components/chain-fee.js'
|
|||||||
import dynamic from 'next/dynamic'
|
import dynamic from 'next/dynamic'
|
||||||
import { HasNewNotesProvider } from '@/components/use-has-new-notes'
|
import { HasNewNotesProvider } from '@/components/use-has-new-notes'
|
||||||
import WalletsProvider from '@/wallets/client/context'
|
import WalletsProvider from '@/wallets/client/context'
|
||||||
|
import FaviconProvider from '@/components/favicon'
|
||||||
|
|
||||||
const PWAPrompt = dynamic(() => import('react-ios-pwa-prompt'), { ssr: false })
|
const PWAPrompt = dynamic(() => import('react-ios-pwa-prompt'), { ssr: false })
|
||||||
|
|
||||||
@ -121,6 +122,7 @@ export default function MyApp ({ Component, pageProps: { ...props } }) {
|
|||||||
<MeProvider me={me}>
|
<MeProvider me={me}>
|
||||||
<WalletsProvider>
|
<WalletsProvider>
|
||||||
<HasNewNotesProvider>
|
<HasNewNotesProvider>
|
||||||
|
<FaviconProvider>
|
||||||
<ServiceWorkerProvider>
|
<ServiceWorkerProvider>
|
||||||
<PriceProvider price={price}>
|
<PriceProvider price={price}>
|
||||||
<AnimationProvider>
|
<AnimationProvider>
|
||||||
@ -139,6 +141,7 @@ export default function MyApp ({ Component, pageProps: { ...props } }) {
|
|||||||
</AnimationProvider>
|
</AnimationProvider>
|
||||||
</PriceProvider>
|
</PriceProvider>
|
||||||
</ServiceWorkerProvider>
|
</ServiceWorkerProvider>
|
||||||
|
</FaviconProvider>
|
||||||
</HasNewNotesProvider>
|
</HasNewNotesProvider>
|
||||||
</WalletsProvider>
|
</WalletsProvider>
|
||||||
</MeProvider>
|
</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