mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(board header): add yellow offline icon for loading online status, red icon for offline status
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -29,7 +29,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.offlineIcon {
|
.offlineIcon {
|
||||||
background-image: url('/public/assets/icons/offline.png');
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
@@ -39,10 +38,6 @@
|
|||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
}
|
}
|
||||||
|
|
||||||
.redOfflineIcon {
|
|
||||||
background-image: url('/public/assets/icons/offline-red.png') !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.boardTitle {
|
.boardTitle {
|
||||||
padding-top: 40px;
|
padding-top: 40px;
|
||||||
|
|||||||
@@ -34,8 +34,7 @@ const BoardHeader = () => {
|
|||||||
const title = isInAllView ? multisubMetadata?.title || 'all' : isInSubscriptionsView ? 'Subscriptions' : subplebbit?.title;
|
const title = isInAllView ? multisubMetadata?.title || 'all' : isInSubscriptionsView ? 'Subscriptions' : subplebbit?.title;
|
||||||
const subtitle = isInAllView ? 'p/all' : isInSubscriptionsView ? 'p/subscriptions' : `p/${address}`;
|
const subtitle = isInAllView ? 'p/all' : isInSubscriptionsView ? 'p/subscriptions' : `p/${address}`;
|
||||||
|
|
||||||
const { isOffline, isDefinitelyOffline, offlineTitle } = useIsSubplebbitOffline(subplebbit);
|
const { isOffline, isOnlineStatusLoading, offlineIconClass, offlineTitle } = useIsSubplebbitOffline(subplebbit);
|
||||||
const iconClass = `${styles.offlineIcon} ${isDefinitelyOffline ? styles.redOfflineIcon : ''}`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
@@ -46,7 +45,7 @@ const BoardHeader = () => {
|
|||||||
)}
|
)}
|
||||||
<div className={styles.boardTitle}>
|
<div className={styles.boardTitle}>
|
||||||
{title || `p/${shortAddress || subplebbitAddress}`}
|
{title || `p/${shortAddress || subplebbitAddress}`}
|
||||||
{isOffline && <span className={`${styles.offlineIcon} ${iconClass}`} title={offlineTitle} />}
|
{(isOffline || isOnlineStatusLoading) && <span className={`${styles.offlineIcon} ${offlineIconClass}`} title={offlineTitle} />}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.boardSubtitle}>{subtitle}</div>
|
<div className={styles.boardSubtitle}>{subtitle}</div>
|
||||||
<hr />
|
<hr />
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ const PostForm = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.postFormDesktop}>
|
<div className={styles.postFormDesktop}>
|
||||||
{!(isInAllView || isInSubscriptionsView) && showForm && isOffline && <div className={styles.offlineBoard}>{offlineTitle}</div>}
|
{!(isInAllView || isInSubscriptionsView) && showForm && (isOffline || isOffline) && <div className={styles.offlineBoard}>{offlineTitle}</div>}
|
||||||
{isThreadClosed ? (
|
{isThreadClosed ? (
|
||||||
<div className={styles.closed}>
|
<div className={styles.closed}>
|
||||||
{t('thread_closed')}
|
{t('thread_closed')}
|
||||||
@@ -297,7 +297,7 @@ const PostForm = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.postFormMobile}>
|
<div className={styles.postFormMobile}>
|
||||||
{!(isInAllView || isInSubscriptionsView) && showForm && isOffline && <div className={styles.offlineBoard}>{offlineTitle}</div>}
|
{!(isInAllView || isInSubscriptionsView) && showForm && (isOffline || isOffline) && <div className={styles.offlineBoard}>{offlineTitle}</div>}
|
||||||
{isThreadClosed ? (
|
{isThreadClosed ? (
|
||||||
<div className={styles.closed}>
|
<div className={styles.closed}>
|
||||||
{t('thread_closed')}
|
{t('thread_closed')}
|
||||||
|
|||||||
@@ -1,19 +1,41 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { Subplebbit } from '@plebbit/plebbit-react-hooks';
|
import { Subplebbit } from '@plebbit/plebbit-react-hooks';
|
||||||
import { getFormattedTimeAgo } from '../lib/utils/time-utils';
|
import { getFormattedTimeAgo } from '../lib/utils/time-utils';
|
||||||
|
import useSubplebbitOfflineStore from '../stores/use-subplebbit-offline-store';
|
||||||
|
|
||||||
const useIsSubplebbitOffline = (subplebbit: Subplebbit) => {
|
const useIsSubplebbitOffline = (subplebbit: Subplebbit) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { updatedAt, updatingState } = subplebbit || {};
|
const { address, state, updatedAt, updatingState } = subplebbit || {};
|
||||||
const isProbablyOffline = !updatedAt || (updatedAt && updatedAt < Date.now() / 1000 - 60 * 60);
|
const { subplebbitOfflineState, setSubplebbitOfflineState, initializesubplebbitOfflineState } = useSubplebbitOfflineStore();
|
||||||
const isDefinitelyOffline = updatingState === 'failed';
|
|
||||||
const isOffline = isProbablyOffline || isDefinitelyOffline;
|
|
||||||
|
|
||||||
const offlineTitle = updatedAt
|
useEffect(() => {
|
||||||
|
if (address && !subplebbitOfflineState[address]) {
|
||||||
|
initializesubplebbitOfflineState(address);
|
||||||
|
}
|
||||||
|
}, [address, subplebbitOfflineState, initializesubplebbitOfflineState]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (address) {
|
||||||
|
setSubplebbitOfflineState(address, { state, updatedAt, updatingState });
|
||||||
|
}
|
||||||
|
}, [address, state, updatedAt, updatingState, setSubplebbitOfflineState]);
|
||||||
|
|
||||||
|
const subplebbitData = subplebbitOfflineState[address] || { initialLoad: true };
|
||||||
|
|
||||||
|
const isLoading = (updatingState === 'resolving-address' && state !== 'stopped') || (subplebbitData.initialLoad && updatingState === 'fetching-ipns' && !updatedAt);
|
||||||
|
const isOffline =
|
||||||
|
!isLoading && ((updatedAt && updatedAt < Date.now() / 1000 - 60 * 60) || updatingState === 'failed' || (updatingState === 'fetching-ipns' && !updatedAt));
|
||||||
|
|
||||||
|
const offlineIconClass = isLoading ? 'yellowOfflineIcon' : isOffline ? 'redOfflineIcon' : '';
|
||||||
|
|
||||||
|
const offlineTitle = isLoading
|
||||||
|
? t('loading')
|
||||||
|
: updatedAt
|
||||||
? isOffline && t('posts_last_synced_info', { time: getFormattedTimeAgo(updatedAt), interpolation: { escapeValue: false } })
|
? isOffline && t('posts_last_synced_info', { time: getFormattedTimeAgo(updatedAt), interpolation: { escapeValue: false } })
|
||||||
: t('subplebbit_offline_info');
|
: t('subplebbit_offline_info');
|
||||||
|
|
||||||
return { isOffline, isDefinitelyOffline, offlineTitle };
|
return { isOffline, isOnlineStatusLoading: isLoading, offlineIconClass, offlineTitle };
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useIsSubplebbitOffline;
|
export default useIsSubplebbitOffline;
|
||||||
|
|||||||
@@ -38,6 +38,14 @@ hr {
|
|||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.yellowOfflineIcon {
|
||||||
|
background-image: url('/public/assets/icons/offline-yellow.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
.redOfflineIcon {
|
||||||
|
background-image: url('/public/assets/icons/offline-red.png');
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.button {
|
.button {
|
||||||
font-size: var(--button-font-size-mobile);
|
font-size: var(--button-font-size-mobile);
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
interface SubplebbitOfflineState {
|
||||||
|
state?: string;
|
||||||
|
updatedAt?: number;
|
||||||
|
updatingState?: string;
|
||||||
|
initialLoad: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SubplebbitOfflineStore {
|
||||||
|
subplebbitOfflineState: Record<string, SubplebbitOfflineState>;
|
||||||
|
setSubplebbitOfflineState: (address: string, state: Partial<SubplebbitOfflineState>) => void;
|
||||||
|
initializesubplebbitOfflineState: (address: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useSubplebbitOfflineStore = create<SubplebbitOfflineStore>((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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}, 7000);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default useSubplebbitOfflineStore;
|
||||||
@@ -18,12 +18,11 @@ const Board = ({ subplebbit, subplebbits }: { subplebbit: Subplebbit; subplebbit
|
|||||||
const boardLink = useCatalog ? `/p/${address}/catalog` : `/p/${address}`;
|
const boardLink = useCatalog ? `/p/${address}/catalog` : `/p/${address}`;
|
||||||
|
|
||||||
const subplebbitData = subplebbits && subplebbits.find((sub: Subplebbit) => sub?.address === address);
|
const subplebbitData = subplebbits && subplebbits.find((sub: Subplebbit) => sub?.address === address);
|
||||||
const { isDefinitelyOffline, isOffline, offlineTitle } = useIsSubplebbitOffline(subplebbitData);
|
const { isOffline, isOnlineStatusLoading, offlineIconClass, offlineTitle } = useIsSubplebbitOffline(subplebbitData);
|
||||||
const iconClass = `${styles.offlineIcon} ${isDefinitelyOffline ? styles.redOfflineIcon : ''}`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.subplebbit} key={address}>
|
<div className={styles.subplebbit} key={address}>
|
||||||
{isOffline && <span className={`${styles.offlineIcon} ${iconClass}`} title={offlineTitle} />}
|
{(isOffline || isOnlineStatusLoading) && <span className={`${styles.offlineIcon} ${offlineIconClass}`} title={offlineTitle} />}
|
||||||
<Link to={boardLink}>{title || address}</Link>
|
<Link to={boardLink}>{title || address}</Link>
|
||||||
{nsfwTag && <span className={styles.nsfw}> ({t(nsfwTag)})</span>}
|
{nsfwTag && <span className={styles.nsfw}> ({t(nsfwTag)})</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -176,7 +176,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.offlineIcon {
|
.offlineIcon {
|
||||||
background-image: url('/public/assets/icons/offline.png');
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: text-top;
|
vertical-align: text-top;
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
@@ -187,10 +186,6 @@
|
|||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
}
|
}
|
||||||
|
|
||||||
.redOfflineIcon {
|
|
||||||
background-image: url('/public/assets/icons/offline-red.png') !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.boardsBoxContent {
|
.boardsBoxContent {
|
||||||
font-size: 93%;
|
font-size: 93%;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|||||||
Reference in New Issue
Block a user