Refactor animations (#2261)

* Fix fireworks not checking localStorage flag

* Refactor animations

* Don't import unused animations

* Remove unused hook

---------

Co-authored-by: k00b <k00b@stacker.news>
This commit is contained in:
ekzyis 2025-07-07 21:34:37 +02:00 committed by GitHub
parent 3a27057781
commit 18a38d8363
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 111 additions and 79 deletions

View File

@ -7,7 +7,7 @@ import {
setRangeValue,
stringToRgb
} from 'tsparticles-engine'
import useDarkMode from './dark-mode'
import useDarkMode from '@/components/dark-mode'
export const FireworksContext = createContext({
strike: () => {}

View File

@ -0,0 +1,72 @@
import { useCallback, useEffect, useState } from 'react'
import { useMe } from '@/components/me'
import { randInRange } from '@/lib/rand'
// import { LightningProvider, useLightning } from './lightning'
import { FireworksProvider, useFireworks } from './fireworks'
// import { SnowProvider, useSnow } from './snow'
const [SelectedAnimationProvider, useSelectedAnimation] = [
// LightningProvider, useLightning
FireworksProvider, useFireworks
// SnowProvider, useSnow // TODO: the snow animation doesn't seem to work anymore
]
export function AnimationProvider ({ children }) {
return (
<SelectedAnimationProvider>
<AnimationHooks>
{children}
</AnimationHooks>
</SelectedAnimationProvider>
)
}
export function useAnimation () {
const animate = useSelectedAnimation()
return useCallback(() => {
const should = window.localStorage.getItem('lnAnimate') || 'yes'
if (should !== 'yes') return false
animate()
return true
}, [animate])
}
export function useAnimationEnabled () {
const [enabled, setEnabled] = useState(undefined)
useEffect(() => {
const enabled = window.localStorage.getItem('lnAnimate') || 'yes'
setEnabled(enabled === 'yes')
}, [])
const toggleEnabled = useCallback(() => {
setEnabled(enabled => {
const newEnabled = !enabled
window.localStorage.setItem('lnAnimate', newEnabled ? 'yes' : 'no')
return newEnabled
})
}, [])
return [enabled, toggleEnabled]
}
function AnimationHooks ({ children }) {
const { me } = useMe()
const animate = useAnimation()
useEffect(() => {
if (me || window.localStorage.getItem('striked') || window.localStorage.getItem('lnAnimated')) return
const timeout = setTimeout(() => {
const animated = animate()
if (animated) {
window.localStorage.setItem('lnAnimated', 'yep')
}
}, randInRange(3000, 10000))
return () => clearTimeout(timeout)
}, [me?.id, animate])
return children
}

View File

@ -13,16 +13,11 @@ export class LightningProvider extends React.Component {
* @returns boolean indicating whether the strike actually happened, based on user preferences
*/
strike = () => {
const should = window.localStorage.getItem('lnAnimate') || 'yes'
if (should === 'yes') {
this.setState(state => {
return {
bolts: [...state.bolts, <Lightning key={state.bolts.length} onDone={() => this.unstrike(state.bolts.length)} />]
}
})
return true
}
return false
}
unstrike = (index) => {

View File

@ -11,8 +11,6 @@ export const SnowProvider = ({ children }) => {
const [flakes, setFlakes] = useState(Array(1024))
const snow = useCallback(() => {
const should = window.localStorage.getItem('lnAnimate') || 'yes'
if (should === 'yes') {
// amount of flakes to add
const n = Math.floor(randInRange(5, 30))
const newFlakes = [...flakes]
@ -23,9 +21,6 @@ export const SnowProvider = ({ children }) => {
}
setStartIndex(i % MAX_FLAKES)
setFlakes(newFlakes)
return true
}
return false
}, [setFlakes, startIndex])
return (

View File

@ -12,10 +12,10 @@ import No from '@/svgs/no.svg'
import Bolt from '@/svgs/bolt.svg'
import Amboss from '@/svgs/amboss.svg'
import Mempool from '@/svgs/bimi.svg'
import { useEffect, useState } from 'react'
import Rewards from './footer-rewards'
import useDarkMode from './dark-mode'
import ActionTooltip from './action-tooltip'
import { useAnimationEnabled } from '@/components/animation'
const RssPopover = (
<Popover>
@ -145,24 +145,10 @@ const LegalPopover = (
export default function Footer ({ links = true }) {
const [darkMode, darkModeToggle] = useDarkMode()
const [lightning, setLightning] = useState(undefined)
useEffect(() => {
setLightning(window.localStorage.getItem('lnAnimate') || 'yes')
}, [])
const toggleLightning = () => {
if (lightning === 'yes') {
window.localStorage.setItem('lnAnimate', 'no')
setLightning('no')
} else {
window.localStorage.setItem('lnAnimate', 'yes')
setLightning('yes')
}
}
const [animationEnabled, toggleAnimation] = useAnimationEnabled()
const DarkModeIcon = darkMode ? Sun : Moon
const LnIcon = lightning === 'yes' ? No : Bolt
const LnIcon = animationEnabled ? No : Bolt
const version = process.env.NEXT_PUBLIC_COMMIT_HASH
@ -175,8 +161,8 @@ export default function Footer ({ links = true }) {
<ActionTooltip notForm overlayText={`${darkMode ? 'disable' : 'enable'} dark mode`}>
<DarkModeIcon onClick={darkModeToggle} width={20} height={20} className='fill-grey theme' suppressHydrationWarning />
</ActionTooltip>
<ActionTooltip notForm overlayText={`${lightning === 'yes' ? 'disable' : 'enable'} lightning animations`}>
<LnIcon onClick={toggleLightning} width={20} height={20} className='ms-2 fill-grey theme' suppressHydrationWarning />
<ActionTooltip notForm overlayText={`${animationEnabled ? 'disable' : 'enable'} lightning animations`}>
<LnIcon onClick={toggleAnimation} width={20} height={20} className='ms-2 fill-grey theme' suppressHydrationWarning />
</ActionTooltip>
</div>
<div className='mb-0' style={{ fontWeight: 500 }}>

View File

@ -13,7 +13,7 @@ import { ACT_MUTATION } from '@/fragments/paidAction'
import { meAnonSats } from '@/lib/apollo'
import { BoostItemInput } from './adv-post-form'
import { useSendWallets } from '@/wallets/index'
import { useFireworks } from './fireworks'
import { useAnimation } from '@/components/animation'
const defaultTips = [100, 1000, 10_000, 100_000]
@ -96,7 +96,7 @@ export default function ItemAct ({ onClose, item, act = 'TIP', step, children, a
}, [onClose, item.id])
const actor = useAct()
const strike = useFireworks()
const animate = useAnimation()
const onSubmit = useCallback(async ({ amount }) => {
if (abortSignal && zapUndoTrigger({ me, amount })) {
@ -111,7 +111,7 @@ export default function ItemAct ({ onClose, item, act = 'TIP', step, children, a
}
const onPaid = () => {
strike()
animate()
onClose?.()
if (!me) setItemMeAnonSats({ id: item.id, amount })
}
@ -143,7 +143,7 @@ export default function ItemAct ({ onClose, item, act = 'TIP', step, children, a
})
if (error) throw error
addCustomTip(Number(amount))
}, [me, actor, wallets.length, act, item.id, onClose, abortSignal, strike])
}, [me, actor, wallets.length, act, item.id, onClose, abortSignal, animate])
return act === 'BOOST'
? <BoostForm step={step} onSubmit={onSubmit} item={item} inputRef={inputRef} act={act}>{children}</BoostForm>
@ -300,7 +300,7 @@ export function useAct ({ query = ACT_MUTATION, ...options } = {}) {
export function useZap () {
const wallets = useSendWallets()
const act = useAct()
const strike = useFireworks()
const animate = useAnimation()
const toaster = useToast()
return useCallback(async ({ item, me, abortSignal }) => {
@ -314,7 +314,7 @@ export function useZap () {
try {
await abortSignal.pause({ me, amount: sats })
strike()
animate()
// batch zaps if wallet is enabled or using fee credits so they can be executed serially in a single request
const { error } = await act({ variables, optimisticResponse, context: { batch: wallets.length > 0 || me?.privates?.sats > sats } })
if (error) throw error
@ -327,7 +327,7 @@ export function useZap () {
// but right now this toast is noisy for optimistic zaps
console.error(error)
}
}, [act, toaster, strike, wallets])
}, [act, toaster, animate, wallets])
}
export class ActCanceledError extends Error {

View File

@ -14,8 +14,6 @@ import { abbrNum } from '../../lib/format'
import { useServiceWorker } from '../serviceworker'
import { signOut } from 'next-auth/react'
import Badges from '../badge'
import { randInRange } from '../../lib/rand'
import { useFireworks } from '../fireworks'
import LightningIcon from '../../svgs/bolt.svg'
import SearchIcon from '../../svgs/search-line.svg'
import classNames from 'classnames'
@ -400,20 +398,6 @@ export function LoginButtons ({ handleClose }) {
}
export function AnonDropdown ({ path }) {
const strike = useFireworks()
useEffect(() => {
if (!window.localStorage.getItem('striked')) {
const to = setTimeout(() => {
const striked = strike()
if (striked) {
window.localStorage.setItem('striked', 'yep')
}
}, randInRange(3000, 10000))
return () => clearTimeout(to)
}
}, [])
return (
<div className='position-relative'>
<Dropdown className={styles.dropdown} align='end' autoClose>

View File

@ -6,7 +6,7 @@ import { numWithUnits } from '@/lib/format'
import { useShowModal } from './modal'
import { useRoot } from './root'
import { ActCanceledError, useAct } from './item-act'
import { useFireworks } from './fireworks'
import { useAnimation } from '@/components/animation'
import { useToast } from './toast'
import { useSendWallets } from '@/wallets/index'
import { Form, SubmitButton } from './form'
@ -48,7 +48,7 @@ export default function PayBounty ({ children, item }) {
const { me } = useMe()
const showModal = useShowModal()
const root = useRoot()
const strike = useFireworks()
const animate = useAnimation()
const toaster = useToast()
const wallets = useSendWallets()
@ -61,7 +61,7 @@ export default function PayBounty ({ children, item }) {
const handlePayBounty = async onCompleted => {
try {
strike()
animate()
const { error } = await act({ onCompleted })
if (error) throw error
} catch (error) {

View File

@ -10,7 +10,7 @@ import { useRouter } from 'next/dist/client/router'
import { useCallback, useEffect } from 'react'
import { ShowModalProvider } from '@/components/modal'
import ErrorBoundary from '@/components/error-boundary'
import { FireworksProvider } from '@/components/fireworks'
import { AnimationProvider } from '@/components/animation'
import { ToastProvider } from '@/components/toast'
import { ServiceWorkerProvider } from '@/components/serviceworker'
import { SSR } from '@/lib/constants'
@ -116,7 +116,7 @@ export default function MyApp ({ Component, pageProps: { ...props } }) {
<WebLnProvider>
<ServiceWorkerProvider>
<PriceProvider price={price}>
<FireworksProvider>
<AnimationProvider>
<ToastProvider>
<ShowModalProvider>
<BlockHeightProvider blockHeight={blockHeight}>
@ -129,7 +129,7 @@ export default function MyApp ({ Component, pageProps: { ...props } }) {
</BlockHeightProvider>
</ShowModalProvider>
</ToastProvider>
</FireworksProvider>
</AnimationProvider>
</PriceProvider>
</ServiceWorkerProvider>
</WebLnProvider>

View File

@ -2,7 +2,7 @@ import { getGetServerSideProps } from '@/api/ssrApollo'
import CCInfo from '@/components/info/cc'
import { Form, Input, SubmitButton } from '@/components/form'
import { CenterLayout } from '@/components/layout'
import { useFireworks } from '@/components/fireworks'
import { useAnimation } from '@/components/animation'
import { useMe } from '@/components/me'
import { useShowModal } from '@/components/modal'
import { usePaidMutation } from '@/components/use-paid-mutation'
@ -76,7 +76,7 @@ function WithdrawButton ({ className }) {
export function BuyCreditsButton ({ className }) {
const showModal = useShowModal()
const strike = useFireworks()
const animate = useAnimation()
const [buyCredits] = usePaidMutation(BUY_CREDITS)
return (
@ -94,7 +94,7 @@ export function BuyCreditsButton ({ className }) {
credits: Number(amount)
},
onCompleted: () => {
strike()
animate()
}
})
onClose()

View File

@ -13,7 +13,7 @@ import { useShowModal } from '@/components/modal'
import dynamic from 'next/dynamic'
import { FAST_POLL_INTERVAL, SSR } from '@/lib/constants'
import { useToast } from '@/components/toast'
import { useFireworks } from '@/components/fireworks'
import { useAnimation } from '@/components/animation'
import { Col, Row } from 'react-bootstrap'
import { useData } from '@/components/use-data'
import { GrowthPieChartSkeleton } from '@/components/charts-skeletons'
@ -133,7 +133,7 @@ export default function Rewards ({ ssrData }) {
export function DonateButton () {
const showModal = useShowModal()
const toaster = useToast()
const strike = useFireworks()
const animate = useAnimation()
const [donateToRewards] = usePaidMutation(DONATE)
return (
@ -151,7 +151,7 @@ export function DonateButton () {
sats: Number(amount)
},
onCompleted: () => {
strike()
animate()
toaster.success('donated')
}
})