2023-08-19 16:29:57 +02:00
import React , { useEffect , useRef , useState } from "react" ;
2023-08-19 15:05:16 +02:00
import { Link } from "react-router-dom" ;
2023-08-19 16:29:57 +02:00
import { usePublishSubplebbitEdit } from "@plebbit/plebbit-react-hooks" ;
2023-08-19 15:05:16 +02:00
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 }) => {
2023-08-19 16:29:57 +02:00
const {
setCaptchaResponse ,
setChallengesArray ,
setIsCaptchaOpen ,
setResolveCaptchaPromise ,
selectedAddress ,
selectedStyle ,
} = useGeneralStore ( state => state );
2023-08-19 15:05:16 +02:00
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 ));
2023-08-19 16:29:57 +02:00
const [ triggerPublilshSubplebbitEdit , setTriggerPublishCommentEdit ] = useState ( false );
2023-08-19 15:05:16 +02:00
const [, setNewErrorMessage ] = useError ();
const [, setNewSuccessMessage ] = useSuccess ();
2023-08-19 16:29:57 +02:00
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 );
2023-08-19 15:05:16 +02:00
useEffect (() => {
2023-08-19 16:29:57 +02:00
if ( isInitialMount . current ) {
setBoardSettingsJson ( JSON . stringify ( generateSettingsFromSubplebbit ( subplebbit ), null , 2 ));
isInitialMount . current = false ;
}
2023-08-19 15:05:16 +02:00
}, [ subplebbit ]);
2023-08-19 16:29:57 +02:00
2023-08-19 15:05:16 +02:00
function validateSettings ( updatedSettings , allowedSettings ) {
2023-08-19 16:29:57 +02:00
if ( ! allowedSettings ) throw new Error ( `Allowed settings structure does not match updated settings.` );
2023-08-19 15:05:16 +02:00
for ( let key in updatedSettings ) {
if ( ! allowedSettings . hasOwnProperty ( key )) {
throw new Error ( `Unexpected setting: ${ key } ` );
}
if ( typeof updatedSettings [ key ] === 'object' && updatedSettings [ key ] !== null ) {
validateSettings ( updatedSettings [ key ], allowedSettings [ key ]);
}
}
}
2023-08-19 16:29:57 +02:00
const onChallenge = async ( challenges , subplebbitEdit ) => {
let challengeAnswers = [];
2023-08-19 15:05:16 +02:00
try {
2023-08-19 16:29:57 +02:00
challengeAnswers = await getChallengeAnswersFromUser ( challenges )
}
catch ( error ) {
setNewErrorMessage ( error . message ); console . log ( error );
}
if ( challengeAnswers ) {
await subplebbitEdit . publishChallengeAnswers ( challengeAnswers )
2023-08-19 15:05:16 +02:00
}
};
2023-08-19 16:29:57 +02:00
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 , initialSettings );
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 } ` );
}
};
2023-08-19 15:05:16 +02:00
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"
2023-08-19 16:29:57 +02:00
autoComplete = "off"
autoCorrect = "off"
spellCheck = "false"
2023-08-19 15:05:16 +02:00
/>
< 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'
);
}}
>
Board Settings
</ span >
</ span >
]
</>
);
};
export default BoardSettings ;