refactor(state-string): make loading state strings user-friendly, replace gateway hostnames with via IPFS

This commit is contained in:
plebeius
2026-03-09 19:36:54 +08:00
parent 61faa06ff9
commit af89b59cca
2 changed files with 76 additions and 66 deletions
+18 -7
View File
@@ -75,7 +75,7 @@ describe('use-state-string', () => {
container.remove(); container.remove();
}); });
it('formats client state strings with normalized hostnames', () => { it('formats client state strings with friendly names and via IPFS', () => {
testState.clientsStates = { testState.clientsStates = {
'fetching-ipns': ['https://rpc.example.com/path', 'https://ipfs.io/api'], 'fetching-ipns': ['https://rpc.example.com/path', 'https://ipfs.io/api'],
'resolving-address': ['https://ens.example.com'], 'resolving-address': ['https://ens.example.com'],
@@ -85,19 +85,19 @@ describe('use-state-string', () => {
root.render(createElement(StateStringHarness, { value: { state: 'updating' } })); root.render(createElement(StateStringHarness, { value: { state: 'updating' } }));
}); });
expect(latestValue).toBe('Fetching IPNS from rpc.example.com, ipfs.io, resolving address from ens.example.com'); expect(latestValue).toBe('Resolving address, downloading board via IPFS');
}); });
it('falls back to publishing and updating states when no client states are available', () => { it('falls back to publishing and updating states when no client states are available', () => {
act(() => { act(() => {
root.render(createElement(StateStringHarness, { value: { publishingState: 'fetching-ipfs', state: 'publishing' } })); root.render(createElement(StateStringHarness, { value: { publishingState: 'fetching-ipfs', state: 'publishing' } }));
}); });
expect(latestValue).toBe('Downloading thread'); expect(latestValue).toBe('Downloading thread via IPFS');
act(() => { act(() => {
root.render(createElement(StateStringHarness, { value: { state: 'updating', updatingState: 'fetching-ipns' } })); root.render(createElement(StateStringHarness, { value: { state: 'updating', updatingState: 'fetching-ipns' } }));
}); });
expect(latestValue).toBe('Downloading board'); expect(latestValue).toBe('Downloading board via IPFS');
}); });
it('sanitizes single-board feed state strings to board wording', () => { it('sanitizes single-board feed state strings to board wording', () => {
@@ -110,7 +110,7 @@ describe('use-state-string', () => {
root.render(createElement(FeedStateStringHarness, { addresses: ['music-posting.eth'] })); root.render(createElement(FeedStateStringHarness, { addresses: ['music-posting.eth'] }));
}); });
expect(latestValue).toBe('Downloading board'); expect(latestValue).toBe('Downloading board via IPFS');
}); });
it('aggregates multi-board feed states across address resolution, threads, and pages', () => { it('aggregates multi-board feed states across address resolution, threads, and pages', () => {
@@ -137,7 +137,7 @@ describe('use-state-string', () => {
root.render(createElement(FeedStateStringHarness, { addresses: ['music-posting.eth', 'tech-posting.eth'] })); root.render(createElement(FeedStateStringHarness, { addresses: ['music-posting.eth', 'tech-posting.eth'] }));
}); });
expect(latestValue).toBe('Resolving 2 addresses from ens.example.com, downloading 2 boards, 1 threads, 1 page from gateway.example.com, ipfs.io'); expect(latestValue).toBe('Resolving 2 board addresses, downloading 2 boards (music-posting.eth, tech-posting.eth), 1 thread, 1 page via IPFS');
}); });
it('shows an immediate board-specific loading string before detailed multi-board states arrive', () => { it('shows an immediate board-specific loading string before detailed multi-board states arrive', () => {
@@ -145,6 +145,17 @@ describe('use-state-string', () => {
root.render(createElement(FeedStateStringHarness, { addresses: ['music-posting.eth', 'tech-posting.eth'] })); root.render(createElement(FeedStateStringHarness, { addresses: ['music-posting.eth', 'tech-posting.eth'] }));
}); });
expect(latestValue).toBe('Downloading 2 boards'); expect(latestValue).toBe('Downloading 2 boards (music-posting.eth, tech-posting.eth)');
});
it('shortens long hash addresses in board list using getShortAddress', () => {
const longAddr1 = 'AdnytMQQMvAkG3XbzoVyAE6YLmHuG3UDigUC';
const longAddr2 = 'NFgjQWX2EUEsZbzoVyAE6YLmHuG3UDigUCxx';
act(() => {
root.render(createElement(FeedStateStringHarness, { addresses: [longAddr1, longAddr2] }));
});
expect(latestValue).toBe('Downloading 2 boards (MvAkG3XbzoVy, EUEsZbzoVyAE)');
}); });
}); });
+58 -59
View File
@@ -1,6 +1,7 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useClientsStates, useSubplebbit, useSubplebbitsStates } from '@bitsocialnet/bitsocial-react-hooks'; import { useClientsStates, useSubplebbit, useSubplebbitsStates } from '@bitsocialnet/bitsocial-react-hooks';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import getShortAddress from '../lib/get-short-address';
interface CommentOrSubplebbit { interface CommentOrSubplebbit {
state?: string; state?: string;
@@ -12,19 +13,19 @@ interface States {
[key: string]: string[]; [key: string]: string[];
} }
const clientHosts: { [key: string]: string } = {}; const friendlyStateNames: Record<string, string> = {
'fetching-ipns': 'downloading board',
const getClientHost = (clientUrl: string): string => { 'fetching-ipfs': 'downloading thread',
if (!clientHosts[clientUrl]) { 'fetching-subplebbit-ipns': 'downloading board',
try { 'fetching-subplebbit-ipfs': 'downloading board',
clientHosts[clientUrl] = new URL(clientUrl).hostname || clientUrl; 'fetching-update-ipfs': 'downloading update',
} catch { 'resolving-address': 'resolving address',
clientHosts[clientUrl] = clientUrl; 'resolving-subplebbit-address': 'resolving board address',
} 'resolving-author-address': 'resolving author address',
}
return clientHosts[clientUrl];
}; };
const getFriendlyStateName = (state: string): string => friendlyStateNames[state] || state.replaceAll('-', ' ');
const sanitizeSingleFeedLoadingState = (stateString?: string): string | undefined => { const sanitizeSingleFeedLoadingState = (stateString?: string): string | undefined => {
if (!stateString) { if (!stateString) {
return stateString; return stateString;
@@ -47,21 +48,25 @@ const useStateString = (commentOrSubplebbit: CommentOrSubplebbit): string | unde
return useMemo(() => { return useMemo(() => {
let stateString: string | undefined = ''; let stateString: string | undefined = '';
const resolvingParts: string[] = [];
const downloadingParts: string[] = [];
for (const state in debouncedStates) { for (const state in debouncedStates) {
const clientUrls = debouncedStates[state]; if (debouncedStates[state].length === 0) continue;
const clientHosts = clientUrls.map((clientUrl: string) => getClientHost(clientUrl)); const friendlyName = getFriendlyStateName(state);
if (state.includes('resolving')) {
if (clientHosts.length === 0) { resolvingParts.push(friendlyName);
continue; } else {
downloadingParts.push(friendlyName);
} }
}
if (stateString) { if (resolvingParts.length) {
stateString += ', '; stateString = resolvingParts.join(', ');
} }
if (downloadingParts.length) {
const formattedState = state.replaceAll('-', ' ').replace('ipfs', 'IPFS').replace('ipns', 'IPNS'); if (stateString) stateString += ', ';
stateString += `${formattedState} from ${clientHosts.join(', ')}`; stateString += downloadingParts.join(', ') + ' via IPFS';
} }
if (!stateString && commentOrSubplebbit?.state !== 'succeeded') { if (!stateString && commentOrSubplebbit?.state !== 'succeeded') {
@@ -71,6 +76,7 @@ const useStateString = (commentOrSubplebbit: CommentOrSubplebbit): string | unde
stateString = commentOrSubplebbit?.updatingState; stateString = commentOrSubplebbit?.updatingState;
} }
if (stateString) { if (stateString) {
const isIpfsRelated = stateString.includes('ipfs') || stateString.includes('ipns');
stateString = stateString stateString = stateString
.replaceAll('-', ' ') .replaceAll('-', ' ')
.replace('ipfs', 'thread') .replace('ipfs', 'thread')
@@ -78,6 +84,9 @@ const useStateString = (commentOrSubplebbit: CommentOrSubplebbit): string | unde
.replace('fetching', 'downloading') .replace('fetching', 'downloading')
.replace('subplebbit subplebbit', 'board') .replace('subplebbit subplebbit', 'board')
.replace('downloading subplebbit', 'downloading board'); .replace('downloading subplebbit', 'downloading board');
if (isIpfsRelated) {
stateString += ' via IPFS';
}
} }
} }
@@ -103,62 +112,52 @@ export const useFeedStateString = (subplebbitAddresses?: string[]): string | und
return; return;
} }
// e.g. Resolving 2 addresses from infura.io, fetching 2 IPNS, 1 IPFS from cloudflare-ipfs.com, ipfs.io
let stateString = ''; let stateString = '';
if (states['resolving-address']) { if (states['resolving-address']) {
const { subplebbitAddresses, clientUrls } = states['resolving-address']; const { subplebbitAddresses, clientUrls } = states['resolving-address'];
if (subplebbitAddresses.length && clientUrls.length) { if (subplebbitAddresses.length && clientUrls.length) {
stateString += `resolving ${subplebbitAddresses.length} ${subplebbitAddresses.length === 1 ? 'address' : 'addresses'} from ${clientUrls const count = subplebbitAddresses.length;
.map(getClientHost) stateString += `resolving ${count} board ${count === 1 ? 'address' : 'addresses'}`;
.join(', ')}`;
} }
} }
// find all page client and sub addresses const pagesStatesSubplebbitAddresses = new Set<string>();
const pagesStatesClientHosts = new Set();
const pagesStatesSubplebbitAddresses = new Set();
for (const state in states) { for (const state in states) {
if (state.match('page')) { if (state.match('page')) {
states[state].clientUrls.forEach((clientUrl) => pagesStatesClientHosts.add(getClientHost(clientUrl))); states[state].subplebbitAddresses.forEach((subplebbitAddress: string) => pagesStatesSubplebbitAddresses.add(subplebbitAddress));
states[state].subplebbitAddresses.forEach((subplebbitAddress) => pagesStatesSubplebbitAddresses.add(subplebbitAddress));
} }
} }
if (states['fetching-ipns'] || states['fetching-ipfs'] || pagesStatesSubplebbitAddresses.size) { if (states['fetching-ipns'] || states['fetching-ipfs'] || pagesStatesSubplebbitAddresses.size) {
// separate 2 different states using ', ' if (stateString) stateString += ', ';
if (stateString) { stateString += 'downloading ';
stateString += ', '; if (states['fetching-ipns']) {
const count = states['fetching-ipns'].subplebbitAddresses.length;
stateString += `${count} ${count === 1 ? 'board' : 'boards'}`;
if (count <= 5) {
stateString += ` (${states['fetching-ipns'].subplebbitAddresses.map((a: string) => getShortAddress(a) || a).join(', ')})`;
}
} }
if (states['fetching-ipfs']) {
// find all client urls if (states['fetching-ipns']) stateString += ', ';
const clientHosts = new Set(pagesStatesClientHosts); const count = states['fetching-ipfs'].subplebbitAddresses.length;
states['fetching-ipns']?.clientUrls.forEach((clientUrl) => clientHosts.add(getClientHost(clientUrl))); stateString += `${count} ${count === 1 ? 'thread' : 'threads'}`;
states['fetching-ipfs']?.clientUrls.forEach((clientUrl) => clientHosts.add(getClientHost(clientUrl)));
if (clientHosts.size) {
stateString += 'downloading ';
if (states['fetching-ipns']) {
stateString += `${states['fetching-ipns'].subplebbitAddresses.length} boards`;
}
if (states['fetching-ipfs']) {
if (states['fetching-ipns']) {
stateString += ', ';
}
stateString += `${states['fetching-ipfs'].subplebbitAddresses.length} threads`;
}
if (pagesStatesSubplebbitAddresses.size) {
if (states['fetching-ipns'] || states['fetching-ipfs']) {
stateString += ', ';
}
stateString += `${pagesStatesSubplebbitAddresses.size} ${pagesStatesSubplebbitAddresses.size === 1 ? 'page' : 'pages'}`;
}
stateString += ` from ${[...clientHosts].join(', ')}`;
} }
if (pagesStatesSubplebbitAddresses.size) {
if (states['fetching-ipns'] || states['fetching-ipfs']) stateString += ', ';
const count = pagesStatesSubplebbitAddresses.size;
stateString += `${count} ${count === 1 ? 'page' : 'pages'}`;
}
stateString += ' via IPFS';
} }
if (!stateString && subplebbitAddresses?.length) { if (!stateString && subplebbitAddresses?.length) {
stateString = `downloading ${subplebbitAddresses.length} boards`; const count = subplebbitAddresses.length;
stateString = `downloading ${count} ${count === 1 ? 'board' : 'boards'}`;
if (count <= 5) {
stateString += ` (${subplebbitAddresses.map((a) => getShortAddress(a) || a).join(', ')})`;
}
} }
// capitalize first letter // capitalize first letter