Files
5chan/src/components/BoardSettings.jsx
T
2023-08-19 21:27:31 +02:00

326 lines
10 KiB
React
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useEffect, useRef, useState } from "react";
import { Link } from "react-router-dom";
import { usePublishSubplebbitEdit } from "@plebbit/plebbit-react-hooks";
import { StyledModal } from './styled/modals/ModerationModal.styled';
import useError from "../hooks/useError";
import useSuccess from "../hooks/useSuccess";
import useGeneralStore from '../hooks/stores/useGeneralStore';
const BoardSettings = ({ subplebbit }) => {
const {
setCaptchaResponse,
setChallengesArray,
setIsCaptchaOpen,
setResolveCaptchaPromise,
selectedAddress,
selectedStyle,
} = useGeneralStore(state => state);
const allowedSettings = {
address: subplebbit.address,
apiUrl: subplebbit.apiUrl,
description: subplebbit.description,
pubsubTopic: subplebbit.pubsubTopic,
settings: {
fetchThumbnailUrls: subplebbit.settings?.fetchThumbnailUrls,
fetchThumbnailUrlsProxyUrl: subplebbit.settings?.fetchThumbnailUrlsProxyUrl,
},
roles: subplebbit.roles,
rules: subplebbit.rules,
suggested: {
avatarUrl: subplebbit.suggested?.avatarUrl,
backgroundUrl: subplebbit.suggested?.backgroundUrl,
bannerUrl: subplebbit.suggested?.bannerUrl,
language: subplebbit.suggested?.language,
primaryColor: subplebbit.suggested?.primaryColor,
secondaryColor: subplebbit.suggested?.secondaryColor,
},
title: subplebbit.title,
};
const generateSettingsFromSubplebbit = (subplebbitData) => ({
address: subplebbitData.address,
apiUrl: subplebbitData.apiUrl,
description: subplebbitData.description,
pubsubTopic: subplebbitData.pubsubTopic,
settings: {
fetchThumbnailUrls: subplebbitData.settings?.fetchThumbnailUrls,
fetchThumbnailUrlsProxyUrl: subplebbitData.settings?.fetchThumbnailUrlsProxyUrl,
},
roles: subplebbitData.roles,
rules: subplebbitData.rules,
suggested: {
avatarUrl: subplebbitData.suggested?.avatarUrl,
backgroundUrl: subplebbitData.suggested?.backgroundUrl,
bannerUrl: subplebbitData.suggested?.bannerUrl,
language: subplebbitData.suggested?.language,
primaryColor: subplebbitData.suggested?.primaryColor,
secondaryColor: subplebbitData.suggested?.secondaryColor,
},
title: subplebbitData.title,
});
const initialSettings = generateSettingsFromSubplebbit(subplebbit);
const [isModalOpen, setIsModalOpen] = useState(false);
const [boardSettingsJson, setBoardSettingsJson] = useState(JSON.stringify(initialSettings, null, 2));
const [triggerPublilshSubplebbitEdit, setTriggerPublishCommentEdit] = useState(false);
const [, setNewErrorMessage] = useError();
const [, setNewSuccessMessage] = useSuccess();
const getDifferences = (oldObj, newObj) => {
let differences = {};
for (let key in oldObj) {
if (typeof oldObj[key] === 'object' && oldObj[key] !== null) {
const nestedDifferences = getDifferences(oldObj[key], newObj[key] || {});
if (Object.keys(nestedDifferences).length > 0) {
differences[key] = nestedDifferences;
}
} else if (oldObj[key] !== newObj[key]) {
differences[key] = newObj[key];
}
}
for (let key in newObj) {
if (!oldObj.hasOwnProperty(key)) {
differences[key] = newObj[key];
}
}
return differences;
};
const isInitialMount = useRef(true);
useEffect(() => {
if (isInitialMount.current) {
setBoardSettingsJson(JSON.stringify(generateSettingsFromSubplebbit(subplebbit), null, 2));
isInitialMount.current = false;
}
}, [subplebbit]);
function validateSettings(updatedSettings, allowedSettings) {
for (let key in updatedSettings) {
if (!allowedSettings.hasOwnProperty(key) && !initialSettings.hasOwnProperty(key)) {
throw new Error(`Unexpected setting: ${key}`);
}
if (typeof updatedSettings[key] === 'object' && updatedSettings[key] !== null
&& !Array.isArray(updatedSettings[key])) {
if (typeof allowedSettings[key] !== 'object' || allowedSettings[key] === null
|| Array.isArray(allowedSettings[key])) {
throw new Error(`Expected ${key} to be an object in allowedSettings`);
}
validateSettings(updatedSettings[key], allowedSettings[key]);
}
}
}
const onChallenge = async (challenges, subplebbitEdit) => {
let challengeAnswers = [];
try {
challengeAnswers = await getChallengeAnswersFromUser(challenges)
}
catch (error) {
setNewErrorMessage(error.message); console.log(error);
}
if (challengeAnswers) {
await subplebbitEdit.publishChallengeAnswers(challengeAnswers)
}
};
const onChallengeVerification = (challengeVerification) => {
if (challengeVerification.challengeSuccess === true) {
setNewSuccessMessage('Challenge Success');
} else if (challengeVerification.challengeSuccess === false) {
setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`);
console.log('challenge failed', challengeVerification);
}
};
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(setNewErrorMessage('Could not load challenges'));
};
});
};
const [editSubplebbitOptions, setEditSubplebbitOptions] = useState({
subplebbitAddress: selectedAddress,
onChallenge,
onChallengeVerification,
onError: (error) => {
setNewErrorMessage(error.message); console.log(error);
}
});
const { publishSubplebbitEdit } = usePublishSubplebbitEdit(editSubplebbitOptions);
useEffect(() => {
let isActive = true;
if (editSubplebbitOptions && triggerPublilshSubplebbitEdit) {
(async () => {
await publishSubplebbitEdit(editSubplebbitOptions);
if (isActive) {
setTriggerPublishCommentEdit(false);
}
})();
}
return () => {
isActive = false;
};
}, [editSubplebbitOptions, publishSubplebbitEdit, triggerPublilshSubplebbitEdit]);
const handleSaveChanges = async () => {
try {
const updatedSettings = JSON.parse(boardSettingsJson);
validateSettings(updatedSettings, allowedSettings);
const changes = getDifferences(initialSettings, updatedSettings);
if (Object.keys(changes).length > 0) {
setEditSubplebbitOptions(prevOptions => ({
...prevOptions,
...changes
}));
setTriggerPublishCommentEdit(true);
} else {
setNewErrorMessage("No changes detected");
}
} catch (error) {
setNewErrorMessage(`Error saving changes: ${error}`);
console.log(error);
}
};
const handleResetChanges = () => {
setBoardSettingsJson(JSON.stringify(initialSettings, null, 2));
};
function generateSettingsList(settingsObj, parentKey = '') {
let result = [];
for (let key in settingsObj) {
if (typeof settingsObj[key] === 'object' && settingsObj[key] !== null) {
const nestedItems = generateSettingsList(settingsObj[key], `${parentKey}${key}.`);
if (nestedItems.length > 1) {
result.push(`${parentKey}${key}: { ${nestedItems.join(', ')} }`);
} else {
result.push(...nestedItems);
}
} else {
result.push(`${parentKey}${key}`);
}
}
return result;
}
const possibleSettingsList = generateSettingsList(initialSettings);
const handleCloseModal = () => {
setIsModalOpen(false);
};
return (
<>
<StyledModal
isOpen={isModalOpen}
onRequestClose={handleCloseModal}
contentLabel="Board Settings"
style={{ overlay: { backgroundColor: "rgba(0,0,0,.25)" }}}
selectedStyle={selectedStyle}
>
<div className="panel-board">
<div className="panel-header">
Board Settings
<Link to="" onClick={handleCloseModal}>
<span className="icon" title="close" />
</Link>
</div>
<div className="settings-info">
<div>
<strong>Allowed settings: </strong>
<span>
{`{ ${possibleSettingsList.join(', ')} }`}
</span>
</div>
<strong style={{marginTop: '10px', display: 'inline-block'}}>API docs: </strong><a style={{color: 'inherit'}} href="https://github.com/plebbit/plebbit-js#readme" target="_blank" rel="noreferrer">https://github.com/plebbit/plebbit-js#readme</a>
</div>
<textarea
value={boardSettingsJson}
onChange={e => setBoardSettingsJson(e.target.value)}
className="board-settings"
autoComplete="off"
autoCorrect="off"
spellCheck="false"
/>
<div className="button-group">
<button id="reset-board-settings" onClick={handleResetChanges}>Reset</button>
<button id="save-board-settings" onClick={handleSaveChanges}>Save Changes</button>
</div>
</div>
</StyledModal>
 [
<span id="subscribe" style={{ cursor: 'pointer' }}>
<span
onClick={() => {
window.electron && window.electron.isElectron
? setIsModalOpen(true)
: alert(
'To edit this board you must be using the plebchan desktop app, which is a plebbit full node that seeds the board automatically.\n\nDownload plebchan here:\n\nhttps://github.com/plebbit/plebchan/releases/latest'
);
}}
>
Settings
</span>
</span>
]
</>
);
};
export default BoardSettings;