Add/implement SettingsCreatorPage
This commit is contained in:
parent
54b17ee739
commit
2b70ad667b
10 changed files with 314 additions and 1 deletions
|
@ -1860,6 +1860,17 @@
|
|||
"Law URL": "Law URL",
|
||||
"Clarification": "Clarification",
|
||||
"Client name": "Client name",
|
||||
"Creator settings": "Creator settings",
|
||||
"Muted words": "Muted words",
|
||||
"Add words": "Add words",
|
||||
"Suggestions": "Suggestions",
|
||||
"Add words to block": "Add words to block",
|
||||
"Enable comments for channel.": "Enable comments for channel.",
|
||||
"Minimum time gap in seconds for Slow Mode in livestream chat.": "Minimum time gap in seconds for Slow Mode in livestream chat.",
|
||||
"Minimum %lbc% tip amount for comments": "Minimum %lbc% tip amount for comments",
|
||||
"Enabling a minimum amount to comment will force all comments, including livestreams, to have tips associated with them. This can help prevent spam.": "Enabling a minimum amount to comment will force all comments, including livestreams, to have tips associated with them. This can help prevent spam.",
|
||||
"Minimum %lbc% tip amount for hyperchats": "Minimum %lbc% tip amount for hyperchats",
|
||||
"Enabling a minimum amount to hyperchat will force all TIPPED comments to have this value in order to be shown. This still allows regular comments to be posted.": "Enabling a minimum amount to hyperchat will force all TIPPED comments to have this value in order to be shown. This still allows regular comments to be posted.",
|
||||
"We apologize for this inconvenience, but have added this additional step to prevent abuse. Users on VPN or shared connections will continue to see this message and are not eligible for Rewards.": "We apologize for this inconvenience, but have added this additional step to prevent abuse. Users on VPN or shared connections will continue to see this message and are not eligible for Rewards.",
|
||||
"Help LBRY Save Crypto": "Help LBRY Save Crypto",
|
||||
"The US government is attempting to destroy the cryptocurrency industry. Can you help?": "The US government is attempting to destroy the cryptocurrency industry. Can you help?",
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Route, Redirect, Switch, withRouter } from 'react-router-dom';
|
|||
import SettingsPage from 'page/settings';
|
||||
import SettingsNotificationsPage from 'page/settingsNotifications';
|
||||
import SettingsAdvancedPage from 'page/settingsAdvanced';
|
||||
import SettingsCreatorPage from 'page/settingsCreator';
|
||||
import HelpPage from 'page/help';
|
||||
// @if TARGET='app'
|
||||
import BackupPage from 'page/backup';
|
||||
|
@ -283,6 +284,7 @@ function AppRouter(props: Props) {
|
|||
<PrivateRoute {...props} path={`/$/${PAGES.LIBRARY}`} component={LibraryPage} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.TAGS_FOLLOWING_MANAGE}`} component={TagsFollowingManagePage} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_BLOCKED_MUTED}`} component={ListBlockedPage} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_CREATOR}`} component={SettingsCreatorPage} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.CHANNELS}`} component={ChannelsPage} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.LIVESTREAM}`} component={LiveStreamSetupPage} />
|
||||
|
|
|
@ -38,6 +38,7 @@ exports.SETTINGS = 'settings';
|
|||
exports.SETTINGS_NOTIFICATIONS = 'settings/notifications';
|
||||
exports.SETTINGS_ADVANCED = 'settings/advanced';
|
||||
exports.SETTINGS_BLOCKED_MUTED = 'settings/block_and_mute';
|
||||
exports.SETTINGS_CREATOR = 'settings/creator';
|
||||
exports.SHOW = 'show';
|
||||
exports.ACCOUNT = 'account';
|
||||
exports.SEARCH = 'search';
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
selectLanguage,
|
||||
selectShowMatureContent,
|
||||
} from 'redux/selectors/settings';
|
||||
import { doWalletStatus, selectWalletIsEncrypted, SETTINGS } from 'lbry-redux';
|
||||
import { doWalletStatus, selectMyChannelUrls, selectWalletIsEncrypted, SETTINGS } from 'lbry-redux';
|
||||
import SettingsPage from './view';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
|
||||
|
@ -37,6 +37,7 @@ const select = (state) => ({
|
|||
hideReposts: makeSelectClientSetting(SETTINGS.HIDE_REPOSTS)(state),
|
||||
darkModeTimes: makeSelectClientSetting(SETTINGS.DARK_MODE_TIMES)(state),
|
||||
language: selectLanguage(state),
|
||||
myChannelUrls: selectMyChannelUrls(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
|
|
|
@ -71,6 +71,7 @@ type Props = {
|
|||
language?: string,
|
||||
enterSettings: () => void,
|
||||
exitSettings: () => void,
|
||||
myChannelUrls: ?Array<string>,
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
@ -187,6 +188,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
darkModeTimes,
|
||||
clearCache,
|
||||
openModal,
|
||||
myChannelUrls,
|
||||
} = this.props;
|
||||
const { storedPassword } = this.state;
|
||||
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
|
||||
|
@ -468,6 +470,22 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
}
|
||||
/>
|
||||
|
||||
{myChannelUrls && myChannelUrls.length > 0 && (
|
||||
<Card
|
||||
title={__('Creator settings')}
|
||||
actions={
|
||||
<div className="section__actions">
|
||||
<Button
|
||||
button="secondary"
|
||||
label={__('Manage')}
|
||||
icon={ICONS.SETTINGS}
|
||||
navigate={`/$/${PAGES.SETTINGS_CREATOR}`}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Card
|
||||
title={__('Advanced settings')}
|
||||
actions={
|
||||
|
|
30
ui/page/settingsCreator/index.js
Normal file
30
ui/page/settingsCreator/index.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { connect } from 'react-redux';
|
||||
import SettingsCreatorPage from './view';
|
||||
import {
|
||||
doCommentBlockWords,
|
||||
doCommentUnblockWords,
|
||||
doFetchCreatorSettings,
|
||||
doUpdateCreatorSettings,
|
||||
} from 'redux/actions/comments';
|
||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
import {
|
||||
selectSettingsByChannelId,
|
||||
selectFetchingCreatorSettings,
|
||||
selectFetchingBlockedWords,
|
||||
} from 'redux/selectors/comments';
|
||||
|
||||
const select = (state) => ({
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
settingsByChannelId: selectSettingsByChannelId(state),
|
||||
fetchingCreatorSettings: selectFetchingCreatorSettings(state),
|
||||
fetchingBlockedWords: selectFetchingBlockedWords(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
commentBlockWords: (channelClaim, words) => dispatch(doCommentBlockWords(channelClaim, words)),
|
||||
commentUnblockWords: (channelClaim, words) => dispatch(doCommentUnblockWords(channelClaim, words)),
|
||||
fetchCreatorSettings: (channelClaimIds) => dispatch(doFetchCreatorSettings(channelClaimIds)),
|
||||
updateCreatorSettings: (channelClaim, settings) => dispatch(doUpdateCreatorSettings(channelClaim, settings)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(SettingsCreatorPage);
|
226
ui/page/settingsCreator/view.jsx
Normal file
226
ui/page/settingsCreator/view.jsx
Normal file
|
@ -0,0 +1,226 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import Card from 'component/common/card';
|
||||
import TagsSearch from 'component/tagsSearch';
|
||||
import Page from 'component/page';
|
||||
import ChannelSelector from 'component/channelSelector';
|
||||
import Spinner from 'component/spinner';
|
||||
import { FormField } from 'component/common/form-components/form-field';
|
||||
import LbcSymbol from 'component/common/lbc-symbol';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
|
||||
const DEBOUNCE_REFRESH_MS = 1000;
|
||||
|
||||
type Props = {
|
||||
activeChannelClaim: ChannelClaim,
|
||||
settingsByChannelId: { [string]: PerChannelSettings },
|
||||
fetchingCreatorSettings: boolean,
|
||||
fetchingBlockedWords: boolean,
|
||||
commentBlockWords: (ChannelClaim, Array<string>) => void,
|
||||
commentUnblockWords: (ChannelClaim, Array<string>) => void,
|
||||
fetchCreatorSettings: (Array<string>) => void,
|
||||
updateCreatorSettings: (ChannelClaim, PerChannelSettings) => void,
|
||||
};
|
||||
|
||||
export default function SettingsCreatorPage(props: Props) {
|
||||
const {
|
||||
activeChannelClaim,
|
||||
settingsByChannelId,
|
||||
commentBlockWords,
|
||||
commentUnblockWords,
|
||||
fetchCreatorSettings,
|
||||
updateCreatorSettings,
|
||||
} = props;
|
||||
|
||||
const [commentsEnabled, setCommentsEnabled] = React.useState(true);
|
||||
const [mutedWordTags, setMutedWordTags] = React.useState([]);
|
||||
const [minTipAmountComment, setMinTipAmountComment] = React.useState(0);
|
||||
const [minTipAmountSuperChat, setMinTipAmountSuperChat] = React.useState(0);
|
||||
const [slowModeMinGap, setSlowModeMinGap] = React.useState(0);
|
||||
const [lastUpdated, setLastUpdated] = React.useState(1);
|
||||
|
||||
function settingsToStates(settings: PerChannelSettings) {
|
||||
if (settings.comments_enabled !== undefined) {
|
||||
setCommentsEnabled(settings.comments_enabled);
|
||||
}
|
||||
if (settings.min_tip_amount_comment !== undefined) {
|
||||
setMinTipAmountComment(settings.min_tip_amount_comment);
|
||||
}
|
||||
if (settings.min_tip_amount_super_chat !== undefined) {
|
||||
setMinTipAmountSuperChat(settings.min_tip_amount_super_chat);
|
||||
}
|
||||
if (settings.slow_mode_min_gap !== undefined) {
|
||||
setSlowModeMinGap(settings.slow_mode_min_gap);
|
||||
}
|
||||
if (settings.words) {
|
||||
const tagArray = Array.from(new Set(settings.words));
|
||||
setMutedWordTags(
|
||||
tagArray
|
||||
.filter((t) => t !== '')
|
||||
.map((x) => {
|
||||
return { name: x };
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function setSettings(newSettings: PerChannelSettings) {
|
||||
settingsToStates(newSettings);
|
||||
updateCreatorSettings(activeChannelClaim, newSettings);
|
||||
setLastUpdated(Date.now());
|
||||
}
|
||||
|
||||
function addMutedWords(newTags: Array<Tag>) {
|
||||
const validatedNewTags = [];
|
||||
newTags.forEach((newTag) => {
|
||||
if (!mutedWordTags.some((tag) => tag.name === newTag.name)) {
|
||||
validatedNewTags.push(newTag);
|
||||
}
|
||||
});
|
||||
|
||||
if (validatedNewTags.length !== 0) {
|
||||
setMutedWordTags([...mutedWordTags, ...validatedNewTags]);
|
||||
commentBlockWords(
|
||||
activeChannelClaim,
|
||||
validatedNewTags.map((x) => x.name)
|
||||
);
|
||||
setLastUpdated(Date.now());
|
||||
}
|
||||
}
|
||||
|
||||
function removeMutedWord(tagToRemove: Tag) {
|
||||
const newMutedWordTags = mutedWordTags.slice().filter((t) => t.name !== tagToRemove.name);
|
||||
setMutedWordTags(newMutedWordTags);
|
||||
commentUnblockWords(activeChannelClaim, ['', tagToRemove.name]);
|
||||
setLastUpdated(Date.now());
|
||||
}
|
||||
|
||||
// Update local states with data from API.
|
||||
React.useEffect(() => {
|
||||
if (lastUpdated !== 0 && Date.now() - lastUpdated < DEBOUNCE_REFRESH_MS) {
|
||||
// Still debouncing. Skip update.
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeChannelClaim && settingsByChannelId && settingsByChannelId[activeChannelClaim.claim_id]) {
|
||||
const channelSettings = settingsByChannelId[activeChannelClaim.claim_id];
|
||||
settingsToStates(channelSettings);
|
||||
}
|
||||
}, [activeChannelClaim, settingsByChannelId, lastUpdated]);
|
||||
|
||||
// Re-sync list, mainly to correct any invalid settings.
|
||||
React.useEffect(() => {
|
||||
if (lastUpdated && activeChannelClaim) {
|
||||
const timer = setTimeout(() => {
|
||||
fetchCreatorSettings([activeChannelClaim.claim_id]);
|
||||
}, DEBOUNCE_REFRESH_MS);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [lastUpdated, activeChannelClaim, fetchCreatorSettings]);
|
||||
|
||||
const isBusy = !activeChannelClaim || !settingsByChannelId || !settingsByChannelId[activeChannelClaim.claim_id];
|
||||
|
||||
return (
|
||||
<Page
|
||||
noFooter
|
||||
noSideNavigation
|
||||
backout={{
|
||||
title: __('Creator settings'),
|
||||
backLabel: __('Done'),
|
||||
}}
|
||||
className="card-stack"
|
||||
>
|
||||
<ChannelSelector hideAnon />
|
||||
{isBusy && (
|
||||
<div className="main--empty">
|
||||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
{!isBusy && (
|
||||
<>
|
||||
<Card
|
||||
title={__('General')}
|
||||
actions={
|
||||
<>
|
||||
<FormField
|
||||
type="checkbox"
|
||||
name="comments_enabled"
|
||||
label={__('Enable comments for channel.')}
|
||||
checked={commentsEnabled}
|
||||
onChange={() => setSettings({ comments_enabled: !commentsEnabled })}
|
||||
/>
|
||||
<FormField
|
||||
name="slow_mode_min_gap"
|
||||
label={__('Minimum time gap in seconds for Slow Mode in livestream chat.')}
|
||||
min={0}
|
||||
step={1}
|
||||
type="number"
|
||||
placeholder="1"
|
||||
value={slowModeMinGap}
|
||||
onChange={(e) => setSettings({ slow_mode_min_gap: e.target.value })}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
title={__('Filter')}
|
||||
actions={
|
||||
<div className="tag--blocked-words">
|
||||
<TagsSearch
|
||||
label={__('Muted words')}
|
||||
labelAddNew={__('Add words')}
|
||||
labelSuggestions={__('Suggestions')}
|
||||
onRemove={removeMutedWord}
|
||||
onSelect={addMutedWords}
|
||||
disableAutoFocus
|
||||
tagsPassedIn={mutedWordTags}
|
||||
placeholder={__('Add words to block')}
|
||||
hideSuggestions
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
title={__('Tip')}
|
||||
actions={
|
||||
<>
|
||||
<FormField
|
||||
name="min_tip_amount_comment"
|
||||
label={
|
||||
<I18nMessage tokens={{ lbc: <LbcSymbol /> }}>Minimum %lbc% tip amount for comments</I18nMessage>
|
||||
}
|
||||
helper={__(
|
||||
'Enabling a minimum amount to comment will force all comments, including livestreams, to have tips associated with them. This can help prevent spam.'
|
||||
)}
|
||||
className="form-field--price-amount"
|
||||
min={0}
|
||||
step="any"
|
||||
type="number"
|
||||
placeholder="1"
|
||||
value={minTipAmountComment}
|
||||
onChange={(e) => setSettings({ min_tip_amount_comment: parseFloat(e.target.value) })}
|
||||
/>
|
||||
<FormField
|
||||
name="min_tip_amount_super_chat"
|
||||
label={
|
||||
<I18nMessage tokens={{ lbc: <LbcSymbol /> }}>Minimum %lbc% tip amount for hyperchats</I18nMessage>
|
||||
}
|
||||
helper={__(
|
||||
'Enabling a minimum amount to hyperchat will force all TIPPED comments to have this value in order to be shown. This still allows regular comments to be posted.'
|
||||
)}
|
||||
className="form-field--price-amount"
|
||||
min={0}
|
||||
step="any"
|
||||
type="number"
|
||||
placeholder="1"
|
||||
value={minTipAmountSuperChat}
|
||||
onChange={(e) => setSettings({ min_tip_amount_super_chat: parseFloat(e.target.value) })}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
}
|
|
@ -73,3 +73,15 @@
|
|||
margin: calc(2rem / 10) calc(2rem / 10);
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
||||
.tag--blocked-words {
|
||||
.tag {
|
||||
color: var(--color-tag-words);
|
||||
background-color: var(--color-tag-words-bg);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-tag-words-hover);
|
||||
background-color: var(--color-tag-words-bg-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -115,6 +115,12 @@
|
|||
--color-tag-hover: var(--color-primary-alt);
|
||||
--color-tag-bg-hover: var(--color-button-primary-bg);
|
||||
|
||||
// Tags (words)
|
||||
--color-tag-words: var(--color-gray-5);
|
||||
--color-tag-words-bg: var(--color-button-alt-bg);
|
||||
--color-tag-words-hover: var(--color-button-alt-text);
|
||||
--color-tag-words-bg-hover: var(--color-button-alt-bg-hover);
|
||||
|
||||
// Menu
|
||||
--color-menu-background: var(--color-header-background);
|
||||
--color-menu-background--active: var(--color-card-background-highlighted);
|
||||
|
|
|
@ -26,6 +26,12 @@
|
|||
--color-text-error: #f87171;
|
||||
--color-error: #61373f;
|
||||
|
||||
// Tags (words)
|
||||
--color-tag-words: var(--color-text);
|
||||
--color-tag-words-bg: var(--color-gray-5);
|
||||
--color-tag-words-hover: var(--color-white);
|
||||
--color-tag-words-bg-hover: var(--color-gray-4);
|
||||
|
||||
// Header
|
||||
--color-header-background: var(--color-gray-8);
|
||||
--color-header-button: var(--color-gray-7);
|
||||
|
|
Loading…
Reference in a new issue