diff --git a/src/hooks/use-is-subplebbit-offline.ts b/src/hooks/use-is-subplebbit-offline.ts new file mode 100644 index 00000000..7e51a13d --- /dev/null +++ b/src/hooks/use-is-subplebbit-offline.ts @@ -0,0 +1,51 @@ +import { useTranslation } from 'react-i18next'; +import { useEffect } from 'react'; +import { Subplebbit } from '@plebbit/plebbit-react-hooks'; +import { getFormattedTimeAgo } from '../lib/utils/time-utils'; +import useSubplebbitOfflineStore from '../stores/use-subplebbit-offline-store'; +import useSubplebbitsLoadingStartTimestamps from '../stores/use-subplebbits-loading-start-timestamps-store'; + +const useIsSubplebbitOffline = (subplebbit: Subplebbit) => { + const { t } = useTranslation(); + const { address, state, updatedAt, updatingState } = subplebbit || {}; + const { subplebbitOfflineState, setSubplebbitOfflineState, initializesubplebbitOfflineState } = useSubplebbitOfflineStore(); + const subplebbitsLoadingStartTimestamps = useSubplebbitsLoadingStartTimestamps([address]); + + useEffect(() => { + if (address && !subplebbitOfflineState[address]) { + initializesubplebbitOfflineState(address); + } + }, [address, subplebbitOfflineState, initializesubplebbitOfflineState]); + + useEffect(() => { + if (address) { + setSubplebbitOfflineState(address, { state, updatedAt, updatingState }); + } + }, [address, state, updatedAt, updatingState, setSubplebbitOfflineState]); + + const subplebbitOfflineStore = subplebbitOfflineState[address] || { initialLoad: true }; + const loadingStartTimestamp = subplebbitsLoadingStartTimestamps[0] || 0; + + const isLoading = subplebbitOfflineStore.initialLoad && (!updatedAt || Date.now() / 1000 - updatedAt >= 60 * 60) && Date.now() / 1000 - loadingStartTimestamp < 30; + + const isOffline = !isLoading && ((updatedAt && updatedAt < Date.now() / 1000 - 60 * 60) || (!updatedAt && Date.now() / 1000 - loadingStartTimestamp >= 30)); + + const isOnline = updatedAt && Date.now() / 1000 - updatedAt < 60 * 60; + + const offlineTitle = isLoading + ? t('loading') + : updatedAt + ? isOffline && t('posts_last_synced_info', { time: getFormattedTimeAgo(updatedAt), interpolation: { escapeValue: false } }) + : t('subplebbit_offline_info'); + + // ensure isOffline is false until we have enough information + const hasEnoughInfo = subplebbitOfflineStore.initialLoad === false || updatedAt !== undefined; + + return { + isOffline: hasEnoughInfo && !isOnline && isOffline, + isOnlineStatusLoading: !isOnline && isLoading, + offlineTitle, + }; +}; + +export default useIsSubplebbitOffline; diff --git a/src/stores/use-subplebbit-offline-store.ts b/src/stores/use-subplebbit-offline-store.ts new file mode 100644 index 00000000..abaa6979 --- /dev/null +++ b/src/stores/use-subplebbit-offline-store.ts @@ -0,0 +1,51 @@ +import { create } from 'zustand'; + +interface SubplebbitOfflineState { + state?: string; + updatedAt?: number; + updatingState?: string; + initialLoad: boolean; +} + +interface SubplebbitOfflineStore { + subplebbitOfflineState: Record; + setSubplebbitOfflineState: (address: string, state: Partial) => void; + initializesubplebbitOfflineState: (address: string) => void; +} + +const useSubplebbitOfflineStore = create((set) => ({ + subplebbitOfflineState: {}, + setSubplebbitOfflineState: (address, newState) => + set((state) => ({ + subplebbitOfflineState: { + ...state.subplebbitOfflineState, + [address]: { + ...state.subplebbitOfflineState[address], + ...newState, + }, + }, + })), + initializesubplebbitOfflineState: (address) => { + set((state) => ({ + subplebbitOfflineState: { + ...state.subplebbitOfflineState, + [address]: { + initialLoad: true, + }, + }, + })); + setTimeout(() => { + set((state) => ({ + subplebbitOfflineState: { + ...state.subplebbitOfflineState, + [address]: { + ...state.subplebbitOfflineState[address], + initialLoad: false, + }, + }, + })); + }, 30000); + }, +})); + +export default useSubplebbitOfflineStore; diff --git a/src/stores/use-subplebbits-loading-start-timestamps-store.ts b/src/stores/use-subplebbits-loading-start-timestamps-store.ts new file mode 100644 index 00000000..80ecdb5b --- /dev/null +++ b/src/stores/use-subplebbits-loading-start-timestamps-store.ts @@ -0,0 +1,42 @@ +import { useEffect, useMemo } from 'react'; +import { create } from 'zustand'; + +interface SubplebbitsLoadingStartTimestampsState { + timestamps: Record; + addSubplebbits: (subplebbitAddresses: string[]) => void; +} + +const useSubplebbitsLoadingStartTimestampsStore = create((set, get) => ({ + timestamps: {}, + addSubplebbits: (subplebbitAddresses) => { + const { timestamps } = get(); + const newTimestamps: Record = {}; + subplebbitAddresses.forEach((subplebbitAddress) => { + if (!timestamps[subplebbitAddress]) { + newTimestamps[subplebbitAddress] = Math.round(Date.now() / 1000); + } + }); + if (Object.keys(newTimestamps).length) { + set((state) => ({ timestamps: { ...state.timestamps, ...newTimestamps } })); + } + }, +})); + +const useSubplebbitsLoadingStartTimestamps = (subplebbitAddresses?: string[]) => { + const timestampsStore = useSubplebbitsLoadingStartTimestampsStore((state) => state.timestamps); + const addSubplebbits = useSubplebbitsLoadingStartTimestampsStore((state) => state.addSubplebbits); + + useEffect(() => { + if (subplebbitAddresses) { + addSubplebbits(subplebbitAddresses); + } + }, [subplebbitAddresses, addSubplebbits]); + + const subplebbitsLoadingStartTimestamps = useMemo(() => { + return subplebbitAddresses?.map((subplebbitAddress) => timestampsStore[subplebbitAddress]) || []; + }, [timestampsStore, subplebbitAddresses]); + + return subplebbitsLoadingStartTimestamps; +}; + +export default useSubplebbitsLoadingStartTimestamps; diff --git a/src/views/submit-page/submit-page.tsx b/src/views/submit-page/submit-page.tsx index 6c1fcf6c..a50b8609 100644 --- a/src/views/submit-page/submit-page.tsx +++ b/src/views/submit-page/submit-page.tsx @@ -11,6 +11,7 @@ import { isSubmitView } from '../../lib/utils/view-utils'; import Embed from '../../components/post/embed'; import Markdown from '../../components/markdown'; import styles from './submit-page.module.css'; +import useIsSubplebbitOffline from '../../hooks/use-is-subplebbit-offline'; const UrlField = () => { const { t } = useTranslation(); @@ -87,23 +88,7 @@ const Submit = () => { const { subscriptions } = account || {}; const defaultSubplebbitAddresses = useDefaultSubplebbitAddresses(); - const [isOffline, setIsOffline] = useState(false); - - useEffect(() => { - const checkOfflineStatus = () => { - if (subplebbit?.updatedAt !== undefined) { - setIsOffline(subplebbit.updatedAt < Date.now() / 1000 - 60 * 60); - } else { - setTimeout(() => { - setIsOffline(subplebbit?.updatedAt === undefined || subplebbit.updatedAt < Date.now() / 1000 - 60 * 60); - }, 5000); - } - }; - - if (subplebbitAddress) { - checkOfflineStatus(); - } - }, [subplebbit?.updatedAt, subplebbitAddress]); + const { isOffline, offlineTitle } = useIsSubplebbitOffline(subplebbit); const onPublish = () => { if (!title) { @@ -235,7 +220,7 @@ const Submit = () => { return (
- {isOffline &&
test
} + {isOffline && selectedSubplebbit &&
{offlineTitle}
}