perf: rewrite plebchan completely

This commit is contained in:
plebeius.eth
2024-02-27 17:58:08 +01:00
parent 8f1831b69f
commit c4a5cfec49
95 changed files with 2658 additions and 39527 deletions
-17
View File
@@ -1,17 +0,0 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
const useAnonModeStore = create(
persist(
(set) => ({
anonymousMode: true,
setAnonymousMode: (mode) => set({ anonymousMode: mode }),
}),
{
name: 'anonmode_store',
getStorage: () => localStorage,
},
),
);
export default useAnonModeStore;
-123
View File
@@ -1,123 +0,0 @@
import { create } from 'zustand';
const useGeneralStore = create((set) => ({
bodyStyle: JSON.parse(localStorage.getItem('bodyStyle')) || {
background: '#ffe url(assets/fade.png) top repeat-x',
color: 'maroon',
fontFamily: 'Helvetica, Arial, sans-serif',
},
setBodyStyle: (bodyStyle) => {
localStorage.setItem('bodyStyle', JSON.stringify(bodyStyle));
set(() => ({ bodyStyle }));
},
captchaResponse: '',
setCaptchaResponse: (response) => set({ captchaResponse: response }),
challengesArray: [],
setChallengesArray: (challengesArray) => set({ challengesArray }),
defaultSubplebbits: [],
setDefaultSubplebbits: (subplebbits) => set({ defaultSubplebbits: subplebbits }),
defaultNsfwSubplebbits: [],
setDefaultNsfwSubplebbits: (subplebbits) => set({ defaultNsfwSubplebbits: subplebbits }),
deletePost: false,
setDeletePost: (deletePost) => set({ deletePost }),
editedComment: '',
setEditedComment: (comment) => set({ editedComment: comment }),
editedComments: {},
setEditedComments: (comments) => set({ editedComments: comments }),
feedCacheStates: {},
setFeedCacheState: (address, isCached) =>
set((prev) => ({
feedCacheStates: {
...prev.feedCacheStates,
[address]: isCached,
},
})),
isAuthorDelete: false,
setIsAuthorDelete: (isAuthorDelete) => set({ isAuthorDelete }),
isAuthorEdit: false,
setIsAuthorEdit: (isAuthorEdit) => set({ isAuthorEdit }),
isCaptchaOpen: false,
setIsCaptchaOpen: (isOpen) => set({ isCaptchaOpen: isOpen }),
isEditModalOpen: false,
setIsEditModalOpen: (isOpen) => set({ isEditModalOpen: isOpen }),
canModerate: false,
setCanModerate: (canModerate) => set({ canModerate }),
isModerationOpen: false,
setIsModerationOpen: (isOpen) => set({ isModerationOpen: isOpen }),
isModEdit: false,
setIsModEdit: (isModEdit) => set({ isModEdit }),
isSettingsOpen: false,
setIsSettingsOpen: (isOpen) => set({ isSettingsOpen: isOpen }),
moderatingCommentCid: '',
setModeratingCommentCid: (cid) => set({ moderatingCommentCid: cid }),
originalCommentContent: null,
setOriginalCommentContent: (content) => set({ originalCommentContent: content }),
pendingComment: '',
setPendingComment: (comment) => set({ pendingComment: comment }),
pendingCommentIndex: null,
setPendingCommentIndex: (index) => set({ pendingCommentIndex: index }),
publishedComment: '',
setPublishedComment: (comment) => set({ publishedComment: comment }),
replyQuoteCid: '',
setReplyQuoteCid: (cid) => set({ replyQuoteCid: cid }),
resolveCaptchaPromise: null,
setResolveCaptchaPromise: (resolve) => set({ resolveCaptchaPromise: resolve }),
selectedAddress: '',
setSelectedAddress: (address) => set({ selectedAddress: address }),
selectedParentCid: '',
setSelectedParentCid: (parentCid) => set({ selectedParentCid: parentCid }),
selectedShortCid: '',
setSelectedShortCid: (shortCid) => set({ selectedShortCid: shortCid }),
selectedStyle: localStorage.getItem('selectedStyle') || 'Yotsuba',
setSelectedStyle: (style) => {
localStorage.setItem('selectedStyle', style);
set({ selectedStyle: style });
},
selectedText: '',
setSelectedText: (text) => set({ selectedText: text }),
selectedThread: '',
setSelectedThread: (thread) => set({ selectedThread: thread }),
selectedTitle: '',
setSelectedTitle: (title) => set({ selectedTitle: title }),
showPostForm: false,
setShowPostForm: (show) => set({ showPostForm: show }),
showPostFormLink: true,
setShowPostFormLink: (show) => set({ showPostFormLink: show }),
triggerInsertion: 0,
setTriggerInsertion: (trigger) => set({ triggerInsertion: trigger }),
}));
export default useGeneralStore;
-29
View File
@@ -1,29 +0,0 @@
import { useEffect } from 'react';
import { useAccount } from '@plebbit/plebbit-react-hooks';
import useAnonModeStore from './stores/useAnonModeStore';
const useAnonMode = (threadCid, execute) => {
const account = useAccount();
const { anonymousMode } = useAnonModeStore();
useEffect(() => {
const handleAnonMode = async () => {
let storedSigners = JSON.parse(localStorage.getItem('storedSigners')) || {};
if (!anonymousMode) {
if (execute && storedSigners[threadCid]) {
const signerPrivateKey = storedSigners[threadCid];
if (account) {
await account.plebbit.createSigner({ type: 'ed25519', privateKey: signerPrivateKey });
}
}
}
};
handleAnonMode();
}, [threadCid, execute, account, anonymousMode]);
return;
};
export default useAnonMode;
-28
View File
@@ -1,28 +0,0 @@
import { useEffect } from 'react';
import { useAccount } from '@plebbit/plebbit-react-hooks';
import useAnonModeStore from './stores/useAnonModeStore';
const useAnonModeRef = (threadCidRef, execute) => {
const account = useAccount();
const { anonymousMode } = useAnonModeStore();
useEffect(() => {
const handleAnonMode = async () => {
let storedSigners = JSON.parse(localStorage.getItem('storedSigners')) || {};
if (!anonymousMode) {
if (execute && storedSigners[threadCidRef]) {
const signerPrivateKey = storedSigners[threadCidRef];
if (account) {
await account.plebbit.createSigner({ type: 'ed25519', privateKey: signerPrivateKey });
}
}
}
};
handleAnonMode();
}, [threadCidRef, execute, account, anonymousMode]);
return;
};
export default useAnonModeRef;
-14
View File
@@ -1,14 +0,0 @@
import useGeneralStore from './stores/useGeneralStore';
const useClickForm = () => {
const { setShowPostForm, setShowPostFormLink } = useGeneralStore.getState();
const handleClickForm = () => {
setShowPostForm(true);
setShowPostFormLink(false);
};
return handleClickForm;
};
export default useClickForm;
-52
View File
@@ -1,52 +0,0 @@
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
const useError = () => {
const [errorMessage, setErrorMessage] = useState('');
const [renderCount, setRenderCount] = useState(0);
useEffect(() => {
if (errorMessage && errorMessage.length > 0) {
const showErrorToast = () => {
const toastId = toast.error(errorMessage.toString(), {
position: 'top-right',
autoClose: false,
hideProgressBar: true,
closeOnClick: false,
pauseOnHover: false,
draggable: false,
progress: undefined,
theme: 'dark',
});
return () => {
toast.dismiss(toastId);
};
};
const timeoutId = setTimeout(showErrorToast, 500);
return () => {
clearTimeout(timeoutId);
};
}
}, [errorMessage, renderCount]);
const setNewErrorMessage = (error) => {
let message;
if (typeof error === 'string') {
message = error;
} else if (error instanceof Error) {
message = error.message;
} else {
message = JSON.stringify(error);
}
setErrorMessage(message);
setRenderCount((prevCount) => prevCount + 1);
};
return [errorMessage, setNewErrorMessage];
};
export default useError;
-19
View File
@@ -1,19 +0,0 @@
import { useMemo, useRef } from 'react';
const useFeedRows = (feedWithDescriptionAndRules, columnCount) => {
const rowsRef = useRef([]);
return useMemo(() => {
const rows = [];
for (let i = 0; i < feedWithDescriptionAndRules.length; i += columnCount) {
if (rowsRef.current?.[rows.length] && rowsRef.current[rows.length].length === columnCount) {
rows.push(rowsRef.current[rows.length]);
} else {
rows.push(feedWithDescriptionAndRules.slice(i, i + columnCount));
}
}
rowsRef.current = rows;
return rows;
}, [feedWithDescriptionAndRules, columnCount]);
};
export default useFeedRows;
-98
View File
@@ -1,98 +0,0 @@
import { useMemo } from 'react';
import useStateString from './useStateString';
import { useSubplebbit, useSubplebbitsStates } from '@plebbit/plebbit-react-hooks';
const clientHosts = {};
const getClientHost = (clientUrl) => {
if (!clientHosts[clientUrl]) {
try {
clientHosts[clientUrl] = new URL(clientUrl).hostname || clientUrl;
} catch (e) {
clientHosts[clientUrl] = clientUrl;
}
}
return clientHosts[clientUrl];
};
const useFeedStateString = (subplebbitAddresses) => {
// single subplebbit feed state string
const subplebbitAddress = subplebbitAddresses?.length === 1 ? subplebbitAddresses[0] : undefined;
const subplebbit = useSubplebbit({ subplebbitAddress });
const singleSubplebbitFeedStateString = useStateString(subplebbit);
// multiple subplebbit feed state string
const { states } = useSubplebbitsStates({ subplebbitAddresses });
const multipleSubplebbitsFeedStateString = useMemo(() => {
if (subplebbitAddress) {
return;
}
// e.g. Resolving 2 addresses from infura.io, fetching 2 IPNS, 1 IPFS from cloudflare-ipfs.com, ipfs.io
let stateString = '';
if (states['resolving-address']) {
const { subplebbitAddresses, clientUrls } = states['resolving-address'];
if (subplebbitAddresses.length && clientUrls.length) {
stateString += `resolving ${subplebbitAddresses.length} ${subplebbitAddresses.length === 1 ? 'address' : 'addresses'} from ${clientUrls
.map(getClientHost)
.join(', ')}`;
}
}
// find all page client and sub addresses
const pagesStatesClientHosts = new Set();
const pagesStatesSubplebbitAddresses = new Set();
for (const state in states) {
if (state.match('page')) {
states[state].clientUrls.forEach((clientUrl) => pagesStatesClientHosts.add(getClientHost(clientUrl)));
states[state].subplebbitAddresses.forEach((subplebbitAddress) => pagesStatesSubplebbitAddresses.add(subplebbitAddress));
}
}
if (states['fetching-ipns'] || states['fetching-ipfs'] || pagesStatesSubplebbitAddresses.size) {
// separate 2 different states using ', '
if (stateString) {
stateString += ', ';
}
// find all client urls
const clientHosts = new Set([...pagesStatesClientHosts]);
states['fetching-ipns']?.clientUrls.forEach((clientUrl) => clientHosts.add(getClientHost(clientUrl)));
states['fetching-ipfs']?.clientUrls.forEach((clientUrl) => clientHosts.add(getClientHost(clientUrl)));
if (clientHosts.size) {
stateString += 'fetching ';
if (states['fetching-ipns']) {
stateString += `${states['fetching-ipns'].subplebbitAddresses.length} IPNS`;
}
if (states['fetching-ipfs']) {
if (states['fetching-ipns']) {
stateString += ', ';
}
stateString += `${states['fetching-ipfs'].subplebbitAddresses.length} IPFS`;
}
if (pagesStatesSubplebbitAddresses.size) {
if (states['fetching-ipns'] || states['fetching-ipfs']) {
stateString += ', ';
}
stateString += `${pagesStatesSubplebbitAddresses.size} ${pagesStatesSubplebbitAddresses.size === 1 ? 'page' : 'pages'}`;
}
stateString += ` from ${[...clientHosts].join(', ')}`;
}
}
// capitalize first letter
stateString = stateString.charAt(0).toUpperCase() + stateString.slice(1);
// if string is empty, return undefined instead
return stateString === '' ? undefined : stateString;
}, [states, subplebbitAddress]);
if (singleSubplebbitFeedStateString) {
return singleSubplebbitFeedStateString;
}
return multipleSubplebbitsFeedStateString;
};
export default useFeedStateString;
-43
View File
@@ -1,43 +0,0 @@
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
const useInfo = () => {
const [infoMessage, setInfoMessage] = useState('');
const [renderCount, setRenderCount] = useState(0);
useEffect(() => {
if (infoMessage && infoMessage.length > 0) {
const showInfoToast = () => {
const toastId = toast.info(infoMessage.toString(), {
position: 'top-right',
autoClose: false,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: false,
draggable: false,
progress: undefined,
theme: 'dark',
});
return () => {
toast.dismiss(toastId);
};
};
const timeoutId = setTimeout(showInfoToast, 500);
return () => {
clearTimeout(timeoutId);
};
}
}, [infoMessage, renderCount]);
const setNewInfoMessage = (message) => {
setInfoMessage(message);
setRenderCount((prevCount) => prevCount + 1);
};
return [infoMessage, setNewInfoMessage];
};
export default useInfo;
-61
View File
@@ -1,61 +0,0 @@
import { useMemo } from 'react';
import { useClientsStates } from '@plebbit/plebbit-react-hooks';
const clientHosts = {};
const getClientHost = (clientUrl) => {
if (!clientHosts[clientUrl]) {
try {
clientHosts[clientUrl] = new URL(clientUrl).hostname || clientUrl;
} catch (e) {
clientHosts[clientUrl] = clientUrl;
}
}
return clientHosts[clientUrl];
};
const useStateString = (commentOrSubplebbit) => {
const { states } = useClientsStates({ comment: commentOrSubplebbit });
return useMemo(() => {
let stateString = '';
for (const state in states) {
const clientUrls = states[state];
const clientHosts = clientUrls.map((clientUrl) => getClientHost(clientUrl));
// if there are no valid hosts, skip this state
if (clientHosts.length === 0) {
continue;
}
// separate 2 different states using ' '
if (stateString) {
stateString += ', ';
}
// e.g. 'fetching IPFS from cloudflare-ipfs.com, ipfs.io'
const formattedState = state.replaceAll('-', ' ').replace('ipfs', 'IPFS').replace('ipns', 'IPNS');
stateString += `${formattedState} from ${clientHosts.join(', ')}`;
}
// fallback to comment or subplebbit state when possible
if (!stateString && commentOrSubplebbit?.state !== 'succeeded') {
if (commentOrSubplebbit?.publishingState && commentOrSubplebbit?.publishingState !== 'stopped' && commentOrSubplebbit?.publishingState !== 'succeeded') {
stateString = commentOrSubplebbit.publishingState;
} else if (commentOrSubplebbit?.updatingState !== 'stopped' && commentOrSubplebbit?.updatingState !== 'succeeded') {
stateString = commentOrSubplebbit.updatingState;
}
if (stateString) {
stateString = stateString.replaceAll('-', ' ').replace('ipfs', 'IPFS').replace('ipns', 'IPNS');
}
}
// capitalize first letter
if (stateString) {
stateString = stateString.charAt(0).toUpperCase() + stateString.slice(1);
}
// if string is empty, return undefined instead
return stateString === '' ? undefined : stateString;
}, [states, commentOrSubplebbit]);
};
export default useStateString;
-43
View File
@@ -1,43 +0,0 @@
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
const useSuccess = () => {
const [successMessage, setSuccessMessage] = useState('');
const [renderCount, setRenderCount] = useState(0);
useEffect(() => {
if (successMessage && successMessage.length > 0) {
const showSuccessToast = () => {
const toastId = toast.success(successMessage.toString(), {
position: 'top-right',
autoClose: 3000,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: false,
draggable: false,
progress: undefined,
theme: 'dark',
});
return () => {
toast.dismiss(toastId);
};
};
const timeoutId = setTimeout(showSuccessToast, 500);
return () => {
clearTimeout(timeoutId);
};
}
}, [successMessage, renderCount]);
const setNewSuccessMessage = (message) => {
setSuccessMessage(message);
setRenderCount((prevCount) => prevCount + 1);
};
return [successMessage, setNewSuccessMessage];
};
export default useSuccess;
-16
View File
@@ -1,16 +0,0 @@
import { useState, useEffect } from 'react';
export default function useWindowWidth() {
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
useEffect(() => {
function handleResize() {
setWindowWidth(window.innerWidth);
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowWidth;
}