-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' of https://github.com/plebbit/plebchan
- Loading branch information
Showing
10 changed files
with
456 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,326 @@ | ||
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.