mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
316 lines
9.8 KiB
React
316 lines
9.8 KiB
React
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');
|
||
console.log('challenge success', challengeVerification);
|
||
} 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);
|
||
setBoardSettingsJson(JSON.stringify(initialSettings, null, 2));
|
||
};
|
||
|
||
const openModal = () => {
|
||
setIsModalOpen(true);
|
||
setBoardSettingsJson(JSON.stringify(generateSettingsFromSubplebbit(subplebbit), null, 2));
|
||
};
|
||
|
||
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
|
||
? openModal()
|
||
: 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;
|