update directories

This commit is contained in:
Tom
2023-05-24 12:15:43 +02:00
parent 141c4fe54d
commit 148fe8ff0c
24 changed files with 54 additions and 54 deletions
+158
View File
@@ -0,0 +1,158 @@
import React, { useEffect, useState, useRef } from 'react';
import { StyledModal } from '../styled/modals/CaptchaModal.styled';
import useGeneralStore from '../../hooks/stores/useGeneralStore';
import Modal from 'react-modal';
import Draggable from 'react-draggable';
const CaptchaModal = () => {
const {
challengesArray,
pendingComment,
selectedStyle,
setCaptchaResponse,
isCaptchaOpen, setIsCaptchaOpen,
selectedShortCid,
} = useGeneralStore(state => state);
const [imageSources, setImageSources] = useState([]);
const [currentChallengeIndex, setCurrentChallengeIndex] = useState(0);
const [totalChallenges, setTotalChallenges] = useState(0);
const [isMobile, setIsMobile] = useState(window.innerWidth <= 480);
const responseRef = useRef();
const nodeRef = useRef(null);
useEffect(() => {
const handleResize = () => setIsMobile(window.innerWidth <= 480);
window.addEventListener('resize', handleResize);
// Cleanup function
return () => {
window.removeEventListener('resize', handleResize);
};
}, [setIsMobile]);
useEffect(() => {
if (challengesArray) {
const challenges = challengesArray.challenges;
const decryptedChallenges = [];
for (let i = 0; i < challenges?.length; i++) {
const imageString = challenges[i].challenge;
const imageSource = `data:image/png;base64,${imageString}`;
decryptedChallenges.push(imageSource);
}
setImageSources(decryptedChallenges);
setTotalChallenges(decryptedChallenges.length);
}
}, [challengesArray]);
const handleKeyDown = (event) => {
if (event.key === "Enter") {
event.preventDefault();
submitCaptcha();
}
};
const handleReturnKeyDown = () => {
submitCaptcha((response) => {
useGeneralStore.getState().setCaptchaResponse(response);
useGeneralStore.getState().resolveCaptchaPromise(response);
});
};
const submitCaptcha = (callback) => {
setCaptchaResponse(responseRef.current.value);
setIsCaptchaOpen(false);
if (callback) {
callback(responseRef.current.value);
}
};
return (
<StyledModal
isOpen={isCaptchaOpen}
onRequestClose={() => setIsCaptchaOpen(false)}
contentLabel="Captcha Modal"
shouldCloseOnEsc={false}
shouldCloseOnOverlayClick={false}
selectedStyle={selectedStyle}
overlayClassName="hide-modal-overlay">
<Draggable handle=".modal-header" nodeRef={nodeRef} disabled={isMobile}>
<div className="modal-content" ref={nodeRef}>
<div className="modal-header">
{pendingComment.parentCid ?
("Challenges for Reply to c/" + selectedShortCid) :
"Challenges for New Thread"}
<button className="icon" onClick={() => setIsCaptchaOpen(false)} title="close" />
</div>
<div id="form">
{pendingComment.author?.displayName ? (
<div>
<input id="field" type="text" placeholder={pendingComment.author?.displayName} disabled />
</div>
) : null}
{pendingComment.title ? (
<div>
<input id="field" type="text" placeholder={pendingComment.title} disabled />
</div>
) : null}
{pendingComment.content ? (
<div>
<textarea
rows="4"
placeholder={pendingComment.content || "Comment"}
wrap="soft"
disabled
/>
</div>
) : null}
{pendingComment.link ? (
<div>
<input id="field" type="text" placeholder={pendingComment.link} disabled />
</div>
) : null}
<div id="captcha-container">
<input
id="response"
type="text"
autoComplete='off'
placeholder="TYPE THE CAPTCHA HERE AND PRESS ENTER"
ref={responseRef}
onKeyDown={handleKeyDown}
autoFocus />
<img src={imageSources[currentChallengeIndex]} alt="captcha" />
</div>
<div>
<span style={{lineHeight: '1.7'}}>
Challenge {currentChallengeIndex + 1} of {totalChallenges}
</span>
<button
id="nav"
onClick={() => {
if (currentChallengeIndex + 1 < totalChallenges) {
setCurrentChallengeIndex((currentChallengeIndex + 1) % totalChallenges);
} else {
handleReturnKeyDown();
}
}}
>
{currentChallengeIndex + 1 < totalChallenges ? "Next" : "Post"}
</button>
</div>
</div>
</div>
</Draggable>
</StyledModal>
);
};
Modal.setAppElement('#root');
export default CaptchaModal;
+73
View File
@@ -0,0 +1,73 @@
import React, { useEffect, useRef, useState } from 'react';
import { StyledModal } from '../styled/modals/ReplyModal.styled';
import useGeneralStore from '../../hooks/stores/useGeneralStore';
import Modal from 'react-modal';
import Draggable from 'react-draggable';
const EditModal = ({ isOpen, closeModal, originalCommentContent }) => {
const {
setEditedComment,
selectedStyle,
} = useGeneralStore(state => state);
const nodeRef = useRef(null);
const commentRef = useRef();
const [isMobile, setIsMobile] = useState(window.innerWidth <= 480);
useEffect(() => {
const handleResize = () => setIsMobile(window.innerWidth <= 480);
window.addEventListener('resize', handleResize);
// Cleanup function
return () => {
window.removeEventListener('resize', handleResize);
};
}, [setIsMobile]);
const handleSaveEdit = () => {
setEditedComment(commentRef.current.value);
closeModal();
};
return (
<StyledModal
isOpen={isOpen}
onRequestClose={closeModal}
contentLabel="Edit Comment"
shouldCloseOnEsc={false}
shouldCloseOnOverlayClick={isMobile}
selectedStyle={selectedStyle}
style={isMobile ? ({ overlay: { backgroundColor: "rgba(0,0,0,.25)" }}) : ({ overlay: { backgroundColor: "rgba(0,0,0,0)" }})}>
<Draggable handle=".modal-header" nodeRef={nodeRef} disabled={isMobile}>
<div className="modal-content" ref={nodeRef}>
<div className="modal-header">
Edit Comment
<button className="icon" onClick={() => closeModal()} title="close" />
</div>
<div id="form">
<div className="textarea-wrapper">
<textarea className="textarea"
rows="4"
style={{paddingTop: '0'}}
placeholder="Comment"
defaultValue={originalCommentContent}
wrap="soft"
ref={commentRef} />
</div>
<div>
<button id="next" onClick={handleSaveEdit}>Save</button>
</div>
</div>
</div>
</Draggable>
</StyledModal>
);
};
Modal.setAppElement('#root');
export default EditModal;
+236
View File
@@ -0,0 +1,236 @@
import React, { useState, useEffect } from "react";
import Modal from "react-modal";
import { Link } from "react-router-dom";
import { usePublishCommentEdit } from "@plebbit/plebbit-react-hooks";
import { StyledModal } from "../styled/modals/ModerationModal.styled";
import useGeneralStore from "../../hooks/stores/useGeneralStore";
import useError from "../../hooks/useError";
import useSuccess from "../../hooks/useSuccess";
const ModerationModal = ({ isOpen, closeModal, deletePost }) => {
const {
selectedAddress,
selectedStyle,
setCaptchaResponse,
setChallengesArray,
setIsCaptchaOpen,
moderatingCommentCid,
setResolveCaptchaPromise,
} = useGeneralStore(state => state);
const [pin, setPin] = useState(false);
const [deleteThread, setDeleteThread] = useState(deletePost);
const [close, setClose] = useState(false);
const [reason, setReason] = useState('');
const [triggerPublishCommentEdit, setTriggerPublishCommentEdit] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
const [successMessage, setSuccessMessage] = useState(null);
useError(errorMessage, [errorMessage]);
useSuccess(successMessage, [successMessage]);
useEffect(() => {
setDeleteThread(deletePost);
}, [deletePost]);
useEffect(() => {
if (!isOpen) {
setDeleteThread(deletePost);
}
}, [isOpen, deletePost]);
const handleCloseModal = () => {
setDeleteThread(false);
closeModal();
};
const onChallengeVerification = (challengeVerification) => {
if (challengeVerification.challengeSuccess === true) {
setSuccessMessage('Challenge Success');
} else if (challengeVerification.challengeSuccess === false) {
setErrorMessage('Challenge Failed', {reason: challengeVerification.reason, errors: challengeVerification.errors});
}
};
const onChallenge = async (challenges, comment) => {
let challengeAnswers = [];
try {
challengeAnswers = await getChallengeAnswersFromUser(challenges)
}
catch (error) {
setErrorMessage(error);
}
if (challengeAnswers) {
await comment.publishChallengeAnswers(challengeAnswers)
}
};
const getChallengeAnswersFromUser = async (challenges) => {
setChallengesArray(challenges);
return new Promise((resolve, reject) => {
const imageString = challenges?.challenges[0].challenge;
const imageSource = `data:image/png;base64,${imageString}`;
const challengeImg = new Image();
challengeImg.src = imageSource;
challengeImg.onload = () => {
setIsCaptchaOpen(true);
const handleKeyDown = async (event) => {
if (event.key === 'Enter') {
const currentCaptchaResponse = useGeneralStore.getState().captchaResponse;
resolve(currentCaptchaResponse);
setIsCaptchaOpen(false);
document.removeEventListener('keydown', handleKeyDown);
event.preventDefault();
}
};
setCaptchaResponse('');
document.addEventListener('keydown', handleKeyDown);
setResolveCaptchaPromise(resolve);
};
challengeImg.onerror = () => {
reject(setErrorMessage('Could not load challenges'));
};
});
};
const [publishCommentEditOptions, setPublishCommentEditOptions] = useState({
commentCid: moderatingCommentCid,
subplebbitAddress: selectedAddress,
onChallenge,
onChallengeVerification,
onError: (error) => {
setErrorMessage(error);
},
});
const {error, publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions);
useEffect(() => {
if (error) {
setErrorMessage(error);
}
}, [error]);
useEffect(() => {
setPublishCommentEditOptions((prevOptions) => ({
...prevOptions,
commentCid: moderatingCommentCid,
}));
}, [moderatingCommentCid]);
useEffect(() => {
if (publishCommentEditOptions && triggerPublishCommentEdit) {
(async () => {
await publishCommentEdit();
setTriggerPublishCommentEdit(false);
})();
}
}, [publishCommentEditOptions, triggerPublishCommentEdit, publishCommentEdit]);
return (
<StyledModal
isOpen={isOpen}
onRequestClose={handleCloseModal}
contentLabel="Moderator Tools"
style={{ overlay: { backgroundColor: "rgba(0,0,0,.25)" }}}
selectedStyle={selectedStyle}
>
<div className="panel">
<div className="panel-header">
Moderator Tools
<Link to="" onClick={handleCloseModal}>
<span className="icon" title="close" />
</Link>
</div>
<ul className="settings-cat">
<li className="settings-cat-lbl">
<label>
<input type="checkbox" style={{marginRight: "10px"}}
checked={pin} onChange={() => setPin(!pin)} />
Pin post
</label>
</li>
<li className="settings-tip">
Pin the post to make it a sticky, showed at the top of the board even as new posts are submitted.
</li>
</ul>
<ul className="settings-cat">
<li className="settings-cat-lbl">
<label>
<input type="checkbox" style={{marginRight: "10px"}}
checked={deleteThread} onChange={() => setDeleteThread(!deleteThread)} />
Delete post
</label>
</li>
<li className="settings-tip">
The post will no longer visible to other users, but the person who posted it can still see it in their own account.
</li>
</ul>
<ul className="settings-cat">
<li className="settings-cat-lbl">
<label>
<input type="checkbox" style={{marginRight: "10px"}}
checked={close} onChange={() => setClose(!close)} />
Close post
</label>
</li>
<li className="settings-tip">
Closing a post allows users to still see the content, but they cannot add any new replies to it.
</li>
</ul>
<ul className="settings-cat">
<li className="settings-option disc">
Reason
</li>
<li className="settings-tip">
Help people become better posters by giving a short reason why their post was removed.
</li>
<li className="settings-input" style={{marginTop: "-10px"}}>
<textarea value={reason} placeholder="Enter reason here..."
onChange={e => setReason(e.target.value)}/>
</li>
</ul>
<button
className="save-button"
onClick={async () => {
setPublishCommentEditOptions(prevOptions => ({
...prevOptions,
pinned: pin,
removed: deleteThread,
locked: close,
reason: reason
}));
setTriggerPublishCommentEdit(true);
handleCloseModal();
}}
>
Save
</button>
</div>
</StyledModal>
);
}
Modal.setAppElement("#root");
export default ModerationModal;
+253
View File
@@ -0,0 +1,253 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { usePublishComment } from '@plebbit/plebbit-react-hooks';
import { StyledModal } from '../styled/modals/ReplyModal.styled';
import useGeneralStore from '../../hooks/stores/useGeneralStore';
import Modal from 'react-modal';
import Draggable from 'react-draggable';
import useError from '../../hooks/useError';
const ReplyModal = ({ isOpen, closeModal }) => {
const {
captchaResponse, setCaptchaResponse,
setChallengesArray,
setIsCaptchaOpen,
setPendingComment,
setPendingCommentIndex,
setResolveCaptchaPromise,
selectedAddress,
selectedParentCid,
selectedShortCid,
selectedStyle,
} = useGeneralStore(state => state);
const nodeRef = useRef(null);
const nameRef = useRef();
const commentRef = useRef();
const linkRef = useRef();
const [errorMessage, setErrorMessage] = useState(null);
const [triggerPublishComment, setTriggerPublishComment] = useState(false);
const [selectedText, setSelectedText] = useState('');
const [isMobile, setIsMobile] = useState(window.innerWidth <= 480);
useError(errorMessage, [errorMessage]);
useEffect(() => {
const handleResize = () => setIsMobile(window.innerWidth <= 480);
window.addEventListener('resize', handleResize);
// Cleanup function
return () => {
window.removeEventListener('resize', handleResize);
};
}, [setIsMobile]);
const onModalOpen = () => {
if (commentRef.current) {
commentRef.current.focus();
}
};
const getSelectedText = useCallback(() => {
const text = document.getSelection().toString();
setSelectedText(text ? `>${text}\n` : '');
}, []);
useEffect(() => {
if (isOpen) {
getSelectedText();
} else {
setSelectedText('');
}
}, [isOpen, getSelectedText]);
const onChallengeVerification = (challengeVerification) => {
if (challengeVerification.challengeSuccess === true) {
console.log('challenge success');
}
else if (challengeVerification.challengeSuccess === false) {
setErrorMessage('challenge failed', {reason: challengeVerification.reason, errors: challengeVerification.errors});
}
};
const onChallenge = async (challenges, comment) => {
setPendingComment(comment);
let challengeAnswers = [];
try {
challengeAnswers = await getChallengeAnswersFromUser(challenges)
}
catch (error) {
setErrorMessage(error);
}
if (challengeAnswers) {
await comment.publishChallengeAnswers(challengeAnswers)
}
};
useEffect(() => {
setPublishCommentOptions((prevPublishCommentOptions) => ({
...prevPublishCommentOptions,
subplebbitAddress: selectedAddress,
}));
}, [selectedAddress]);
const [publishCommentOptions, setPublishCommentOptions] = useState({
subplebbitAddress: selectedAddress,
onChallenge,
onChallengeVerification,
onError: (error) => {
setErrorMessage(error);
},
});
const { publishComment, index } = usePublishComment(publishCommentOptions);
useEffect(() => {
if (index !== undefined) {
setPendingCommentIndex(index);
}
}, [index, setPendingCommentIndex]);
const resetFields = useCallback(() => {
if (nameRef.current) {
nameRef.current.value = '';
}
if (commentRef.current) {
commentRef.current.value = '';
}
if (linkRef.current) {
linkRef.current.value = '';
}
}, []);
const handleSubmit = async (event) => {
event.preventDefault();
if (
commentRef.current.value === "" &&
linkRef.current.value === ""
) {
setErrorMessage("Please enter a comment or link.");
return;
}
setPublishCommentOptions((prevPublishCommentOptions) => ({
...prevPublishCommentOptions,
author: {
displayName: nameRef.current.value || undefined,
},
content: commentRef.current.value || undefined,
link: linkRef.current.value || undefined,
parentCid: selectedParentCid,
}));
setTriggerPublishComment(true);
};
useEffect(() => {
if (publishCommentOptions && triggerPublishComment) {
(async () => {
await publishComment();
resetFields();
closeModal();
})();
setTriggerPublishComment(false);
}
}, [publishCommentOptions, triggerPublishComment, publishComment, resetFields, closeModal]);
const getChallengeAnswersFromUser = async (challenges) => {
setChallengesArray(challenges);
return new Promise((resolve, reject) => {
const imageString = challenges?.challenges[0].challenge;
const imageSource = `data:image/png;base64,${imageString}`;
const challengeImg = new Image();
challengeImg.src = imageSource;
challengeImg.onload = () => {
setIsCaptchaOpen(true);
const handleKeyDown = async (event) => {
if (event.key === 'Enter') {
const currentCaptchaResponse = captchaResponse;
resolve(currentCaptchaResponse);
setIsCaptchaOpen(false);
document.removeEventListener('keydown', handleKeyDown);
event.preventDefault();
}
};
setCaptchaResponse('');
document.addEventListener('keydown', handleKeyDown);
setResolveCaptchaPromise(resolve);
};
challengeImg.onerror = () => {
reject(setErrorMessage('Could not load challenges'));
};
});
};
return (
<StyledModal
isOpen={isOpen}
onAfterOpen={onModalOpen}
onRequestClose={closeModal}
contentLabel="Reply Modal"
shouldCloseOnEsc={false}
shouldCloseOnOverlayClick={isMobile}
selectedStyle={selectedStyle}
style={isMobile ? ({ overlay: { backgroundColor: "rgba(0,0,0,.25)" }}) : ({ overlay: { backgroundColor: "rgba(0,0,0,0)" }})}>
<Draggable handle=".modal-header" nodeRef={nodeRef} disabled={isMobile}>
<div className="modal-content" ref={nodeRef}>
<div className="modal-header">
Reply to c/{selectedShortCid}
<button className="icon" onClick={() => closeModal()} title="close" />
</div>
<div id="form">
<div>
<input id="name" type="text" placeholder="Name" ref={nameRef} />
</div>
<div>
<input id="name" type="text" placeholder="Embed link" ref={linkRef} />
</div>
<div className="textarea-wrapper">
<span className="fixed-text">{`c/${selectedShortCid}`}</span>
<textarea className="textarea"
rows="4"
placeholder="Comment"
defaultValue={selectedText}
wrap="soft"
ref={commentRef} />
</div>
<div>
<button id="next" onClick={handleSubmit}>Post</button>
</div>
</div>
</div>
</Draggable>
</StyledModal>
);
};
Modal.setAppElement('#root');
export default ReplyModal;
+368
View File
@@ -0,0 +1,368 @@
import React, { useState, useEffect, useRef } from "react";
import { Link, useLocation, useNavigate } from "react-router-dom";
import Modal from "react-modal";
import { deleteCaches, exportAccount, importAccount, setAccount, setActiveAccount, useAccount, useAccounts } from "@plebbit/plebbit-react-hooks";
import { StyledModal } from "../styled/modals/SettingsModal.styled";
import useGeneralStore from "../../hooks/stores/useGeneralStore";
import useError from "../../hooks/useError";
import useSuccess from "../../hooks/useSuccess";
import packageJson from '../../../package.json'
const {version} = packageJson
const SettingsModal = ({ isOpen, closeModal }) => {
const selectedStyle = useGeneralStore(state => state.selectedStyle);
const navigate = useNavigate();
const location = useLocation();
const [expanded, setExpanded] = useState([]);
const [accountJson, setAccountJson] = useState(null);
const [errorMessage, setErrorMessage] = useState(null);
const [successMessage, setSuccessMessage] = useState(null);
useError(errorMessage, [errorMessage]);
useSuccess(successMessage, [successMessage]);
const account = useAccount();
const { accounts } = useAccounts();
const gatewayRef = useRef();
const ipfsRef = useRef();
const pubsubRef = useRef();
const dataPathRef = useRef();
const importRef = useRef();
const isValidURL = (url) => {
try {
new URL(url);
return true;
} catch {
return false;
}
};
const handleSavePlebbitOptions = async () => {
const gatewayUrls = gatewayRef.current.value.split('\n').filter(url => url.trim());
const ipfsClientsOptions = ipfsRef.current.value.split('\n').filter(url => url.trim()) || undefined;
const pubsubClientsOptions = pubsubRef.current.value.split('\n').filter(url => url.trim());
const invalidUrls = [
...gatewayUrls,
...(ipfsClientsOptions || []),
...pubsubClientsOptions,
].filter((url) => !isValidURL(url));
if (invalidUrls.length > 0) {
setErrorMessage(`Invalid URL(s): ${invalidUrls.join(', ')}`);
return;
}
const plebbitOptions = {
ipfsGatewayUrls: gatewayUrls,
ipfsHttpClientsOptions: ipfsClientsOptions,
pubsubHttpClientsOptions: pubsubClientsOptions,
};
try {
await setAccount({ ...account, plebbitOptions });
localStorage.setItem("successToast", "Settings Saved");
window.location.reload();
} catch (error) {
setErrorMessage(error.message);
}
};
const handleResetPlebbitOptions = async () => {
setErrorMessage(null);
setSuccessMessage(null);
const defaultGatewayUrls = [
'https://ipfs.io',
'https://ipfsgateway.xyz',
'https://cloudflare-ipfs.com'
];
const defaultPubsubHttpClientsOptions = ['https://pubsubprovider.xyz/api/v0'];
gatewayRef.current.value = defaultGatewayUrls.join('\n');
ipfsRef.current.value = "";
pubsubRef.current.value = defaultPubsubHttpClientsOptions.join('\n');
dataPathRef.current.value = "";
try {
await setAccount({
...account,
plebbitOptions: {
ipfsGatewayUrls: defaultGatewayUrls,
ipfsHttpClientsOptions: undefined,
pubsubHttpClientsOptions: defaultPubsubHttpClientsOptions,
},
});
localStorage.setItem("successToast", "Settings Reset");
window.location.reload();
} catch (error) {
setErrorMessage(error.message);
}
};
const handleCloseModal = () => {
setAccountJson(null);
if (location.pathname.endsWith("/settings")) {
const newPath = location.pathname.slice(0, -9);
closeModal();
navigate(newPath, { replace: true });
} else {
closeModal();
}
};
const toggleExpanded = (index) => {
setExpanded((prevExpanded) => {
const newExpanded = [...prevExpanded];
if (newExpanded.includes(index)) {
newExpanded.splice(newExpanded.indexOf(index), 1);
} else {
newExpanded.push(index);
}
return newExpanded;
});
};
const expandAll = () => {
if (expanded.length === 3) {
setExpanded([]);
} else {
setExpanded([0, 1, 2]);
}
};
useEffect(() => {
if (localStorage.getItem("cacheCleared") === "true") {
setSuccessMessage("Cache Cleared");
localStorage.removeItem("cacheCleared");
}
}, []);
const handleExport = async () => {
const activeAccountJson = await exportAccount();
setAccountJson(activeAccountJson);
};
const handleImport = async () => {
const accountJson = importRef.current.value;
try {
const parsedJson = JSON.parse(accountJson);
await importAccount(accountJson);
setActiveAccount(parsedJson.account?.name);
setSuccessMessage("Account Imported");
} catch (error) {
setErrorMessage(error.message);
}
};
const handleAccountChange = (e) => {
setActiveAccount(e.target.value);
};
return (
<StyledModal
isOpen={isOpen}
onRequestClose={handleCloseModal}
contentLabel="Settings"
style={{ overlay: { backgroundColor: "rgba(0,0,0,.25)" }}}
selectedStyle={selectedStyle}
expanded={expanded}
>
<div className="panel">
<div className="panel-header">
<span id="version">
v{version}
</span>
Settings
<Link to="" onClick={handleCloseModal}>
<span className="icon" title="close" />
</Link>
</div>
<div className="all-div">
[
<button className="all-button"
onClick={expandAll} style={{all: "unset", cursor: "pointer"}}>
{expanded.length === 3 ? "Collapse All Settings" : "Expand All Settings"}
</button>
]
</div>
{/* <ul>
<li className="settings-cat-lbl">
<span className={`${expanded.includes(0) ? 'minus' : 'plus'}`}
onClick={() => toggleExpanded(0)}
/>
<span className="settings-pointer" style={{cursor: "pointer"}}
onClick={() => toggleExpanded(0)}
>Account</span>
</li>
<ul className="settings-cat" style={{ display: expanded.includes(0) ? 'block' : 'none' }}>
<li>
</li>
</ul>
</ul>*/}
<ul>
<li className="settings-cat-lbl">
<span className={`${expanded.includes(1) ? 'minus' : 'plus'}`}
onClick={() => toggleExpanded(1)}
/>
<span className="settings-pointer" style={{cursor: "pointer"}}
onClick={() => toggleExpanded(1)}
>Account</span>
<div className="plebbit-options-buttons"
style={{ display: expanded.includes(1) ? 'block' : 'none' }}
>
</div>
</li>
<ul className="settings-cat" style={{ display: expanded.includes(1) ? 'block' : 'none' }}>
<li className="settings-option disc">
Export or Import Your Data
</li>
<div className="plebbit-options-buttons"
style={{ display: expanded.includes(1) ? 'block' : 'none' }}
>
<button className="save-button"
onClick={handleExport}>Export</button>
<button className="reset-button"
onClick={handleImport}
>Import</button>
</div>
<li className="settings-tip">
To export, click "Export", then save your account data displayed below in a safe place. To import, paste your account data into the box below, then click "Import".
</li>
<div className="settings-input">
<textarea ref={importRef} value={accountJson} />
</div>
<li className="settings-option disc">
Current Account: {account?.name}
</li>
<li className="settings-tip">
Select a different account to use in the dropdown below.
</li>
</ul>
<ul className="settings-cat" style={{ display: expanded.includes(1) ? 'block' : 'none' }}>
<li>
<div className="settings-input">
<select className="settings-select"
value={account?.name}
onChange={handleAccountChange}
>
{accounts.map((account) => (
<option key={account?.name} value={account?.name}>
{account?.name}
</option>
))}
</select>
</div>
</li>
</ul>
</ul>
<ul>
<li className="settings-cat-lbl">
<span className={`${expanded.includes(2) ? 'minus' : 'plus'}`}
onClick={() => toggleExpanded(2)}
/>
<span className="settings-pointer" style={{cursor: "pointer"}}
onClick={() => toggleExpanded(2)}
>Plebbit Options</span>
<div className="plebbit-options-buttons"
style={{ display: expanded.includes(2) ? 'block' : 'none' }}
>
<button className="save-button"
onClick={handleSavePlebbitOptions}>Save</button>
<button className="reset-button"
onClick={handleResetPlebbitOptions}>Reset</button>
</div>
</li>
<ul className="settings-cat" style={{ display: expanded.includes(2) ? 'block' : 'none' }}>
<li className="settings-option disc">
IPFS Gateway URLs
</li>
<li className="settings-tip">
Optional URLs of IPFS gateways.
</li>
<div className="settings-input">
<textarea placeholder="IPFS Gateway URLs"
defaultValue={account?.plebbitOptions?.ipfsGatewayUrls.join("\n")}
ref={gatewayRef}
/>
</div>
</ul>
<ul className="settings-cat" style={{ display: expanded.includes(2) ? 'block' : 'none' }}>
<li className="settings-option disc">
IPFS HTTP Clients Options</li>
<li className="settings-tip">Optional URLs of IPFS APIs or IpfsHttpClientOptions, 'http://localhost:5001/api/v0' to use a local IPFS node.</li>
<div className="settings-input">
<textarea placeholder="IPFS HTTP Clients Options"
defaultValue={account?.plebbitOptions?.ipfsHttpClientsOptions ? account?.plebbitOptions?.ipfsHttpClientsOptions.join("\n") : ''}
ref={ipfsRef}
/>
</div>
</ul>
<ul className="settings-cat" style={{ display: expanded.includes(2) ? 'block' : 'none' }}>
<li className="settings-option disc">
PubSub HTTP Clients Options</li>
<li className="settings-tip">Optional URLs or IpfsHttpClientOptions used for pubsub publishing when ipfsHttpClientOptions isn't available, like in the browser.</li>
<div className="settings-input">
<textarea placeholder="PubSub HTTP Clients Options"
defaultValue={account?.plebbitOptions?.pubsubHttpClientsOptions.join("\n")}
ref={pubsubRef}
/>
</div>
</ul>
<ul className="settings-cat" style={{ display: expanded.includes(2) ? 'block' : 'none' }}>
<li className="settings-option disc">
Data Path (Node Only)</li>
<li className="settings-tip">Optional folder path to create/resume the user and subplebbit databases.</li>
<div className="settings-input">
<textarea placeholder="Data Path (Node Only)"
ref={dataPathRef}
/>
</div>
</ul>
{/* <ul className="settings-cat" style={{ display: expanded.includes(2) ? 'block' : 'none' }}>
<li className="settings-option disc">
Chain Providers</li>
<li className="settings-tip">Optional provider RPC URLs and chain IDs.</li>
<ul>
<li className="settings-option disc">Ethereum</li>
<li className="settings-option disc">Avalanche</li>
<li className="settings-option disc">Polygon</li>
</ul>
</ul> */}
</ul>
<div>
<button
className="cache-button"
onClick={async () => {
if (window.confirm("Are you sure you want to clear the cache?")) {
await deleteCaches();
localStorage.setItem("cacheCleared", "true");
window.location.reload();
}
}}
>
Clear Cache
</button>
</div>
</div>
</StyledModal>
);
}
Modal.setAppElement("#root");
export default SettingsModal;