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