mirror of
https://github.com/irislib/iris-messenger.git
synced 2024-09-16 16:23:28 +00:00
settings functional components
This commit is contained in:
parent
2534462b4d
commit
209f7a3fad
@ -1,7 +1,9 @@
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { useCallback, useState } from 'preact/hooks';
|
||||
import { route } from 'preact-router';
|
||||
|
||||
import Component from '../../BaseComponent';
|
||||
import Show from '@/components/helpers/Show.tsx';
|
||||
|
||||
import Copy from '../../components/buttons/Copy';
|
||||
import Events from '../../nostr/Events';
|
||||
import Key from '../../nostr/Key';
|
||||
@ -10,22 +12,24 @@ import { translate as t } from '../../translations/Translation.mjs';
|
||||
import Helpers from '../../utils/Helpers.tsx';
|
||||
import { ExistingAccountLogin } from '../Login';
|
||||
|
||||
export default class Account extends Component {
|
||||
onLogoutClick(hasPriv) {
|
||||
const Account = () => {
|
||||
const [showSwitchAccount, setShowSwitchAccount] = useState(false);
|
||||
|
||||
const onLogoutClick = useCallback((hasPriv) => {
|
||||
if (hasPriv) {
|
||||
route('/logout'); // confirmation screen
|
||||
} else {
|
||||
Session.logOut();
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
async onExtensionLoginClick(e) {
|
||||
const onExtensionLoginClick = async (e) => {
|
||||
e.preventDefault();
|
||||
const rpub = await window.nostr.getPublicKey();
|
||||
Key.login({ rpub });
|
||||
}
|
||||
};
|
||||
|
||||
deleteAccount() {
|
||||
const deleteAccount = useCallback(() => {
|
||||
if (confirm(`${t('delete_account')}?`)) {
|
||||
Events.publish({
|
||||
kind: 0,
|
||||
@ -39,90 +43,79 @@ export default class Account extends Component {
|
||||
Session.logOut();
|
||||
}, 1000);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const myPrivHex = Key.getPrivKey();
|
||||
let myPriv32;
|
||||
if (myPrivHex) {
|
||||
myPriv32 = nip19.nsecEncode(myPrivHex);
|
||||
}
|
||||
const myPub = Key.getPubKey();
|
||||
const myNpub = nip19.npubEncode(myPub);
|
||||
const hasPriv = !!Key.getPrivKey();
|
||||
|
||||
render() {
|
||||
const myPrivHex = Key.getPrivKey();
|
||||
let myPriv32;
|
||||
if (myPrivHex) {
|
||||
// eslint-disable-next-line no-undef
|
||||
myPriv32 = nip19.nsecEncode(myPrivHex);
|
||||
}
|
||||
const myPub = Key.getPubKey();
|
||||
// eslint-disable-next-line no-undef
|
||||
const myNpub = nip19.npubEncode(myPub);
|
||||
return (
|
||||
<div class="centered-container">
|
||||
<h2>{t('account')}</h2>
|
||||
<Show when={hasPriv}>
|
||||
<p>
|
||||
<b>{t('save_backup_of_privkey_first')}</b> {t('otherwise_cant_log_in_again')}
|
||||
</p>
|
||||
</Show>
|
||||
<div className="flex gap-2 my-2">
|
||||
<button className="btn btn-sm btn-primary" onClick={() => onLogoutClick(hasPriv)}>
|
||||
{t('log_out')}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={() => setShowSwitchAccount(!showSwitchAccount)}
|
||||
>
|
||||
{t('switch_account')}
|
||||
</button>
|
||||
</div>
|
||||
<Show when={showSwitchAccount}>
|
||||
<p>
|
||||
<ExistingAccountLogin />
|
||||
</p>
|
||||
<p>
|
||||
<a href="#" onClick={onExtensionLoginClick}>
|
||||
{t('nostr_extension_login')}
|
||||
</a>
|
||||
</p>
|
||||
</Show>
|
||||
<h3>{t('public_key')}</h3>
|
||||
<p>
|
||||
<small>{myNpub}</small>
|
||||
</p>
|
||||
<div className="flex gap-2 my-2">
|
||||
<Copy className="btn btn-neutral btn-sm" copyStr={myNpub} text="Copy npub" />
|
||||
<Copy className="btn btn-neutral btn-sm" copyStr={myPub} text="Copy hex" />
|
||||
</div>
|
||||
<h3>{t('private_key')}</h3>
|
||||
<div className="flex gap-2 my-2">
|
||||
<Show when={myPrivHex}>
|
||||
<>
|
||||
<Copy className="btn btn-neutral btn-sm" copyStr={myPriv32} text="Copy nsec" />
|
||||
<Copy className="btn btn-neutral btn-sm" copyStr={myPrivHex} text="Copy hex" />
|
||||
</>
|
||||
</Show>
|
||||
<Show when={!myPrivHex}>
|
||||
<p>{t('private_key_not_present_good')}</p>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={myPrivHex}>
|
||||
<p>{t('private_key_warning')}</p>
|
||||
</Show>
|
||||
<Show when={Helpers.isStandalone()}>
|
||||
<h3>{t('delete_account')}</h3>
|
||||
<p>
|
||||
<button className="btn btn-sm btn-danger" onClick={deleteAccount}>
|
||||
{t('delete_account')}
|
||||
</button>
|
||||
</p>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const hasPriv = !!Key.getPrivKey();
|
||||
return (
|
||||
<>
|
||||
<div class="centered-container">
|
||||
<h2>{t('account')}</h2>
|
||||
{hasPriv ? (
|
||||
<p>
|
||||
<b>{t('save_backup_of_privkey_first')}</b> {t('otherwise_cant_log_in_again')}
|
||||
</p>
|
||||
) : null}
|
||||
<div className="flex gap-2 my-2">
|
||||
<button className="btn btn-sm btn-primary" onClick={() => this.onLogoutClick(hasPriv)}>
|
||||
{t('log_out')}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={() =>
|
||||
this.setState({
|
||||
showSwitchAccount: !this.state.showSwitchAccount,
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('switch_account')}
|
||||
</button>
|
||||
</div>
|
||||
{this.state.showSwitchAccount && (
|
||||
<>
|
||||
<p>
|
||||
<ExistingAccountLogin />
|
||||
</p>
|
||||
<p>
|
||||
<a href="#" onClick={(e) => this.onExtensionLoginClick(e)}>
|
||||
{t('nostr_extension_login')}
|
||||
</a>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
<h3>{t('public_key')}</h3>
|
||||
<p>
|
||||
<small>{myNpub}</small>
|
||||
</p>
|
||||
<div className="flex gap-2 my-2">
|
||||
<Copy className="btn btn-neutral btn-sm" copyStr={myNpub} text="Copy npub" />
|
||||
<Copy className="btn btn-neutral btn-sm" copyStr={myPub} text="Copy hex" />
|
||||
</div>
|
||||
<h3>{t('private_key')}</h3>
|
||||
<div className="flex gap-2 my-2">
|
||||
{myPrivHex ? (
|
||||
<>
|
||||
<Copy className="btn btn-neutral btn-sm" copyStr={myPriv32} text="Copy nsec" />
|
||||
<Copy className="btn btn-neutral btn-sm" copyStr={myPrivHex} text="Copy hex" />
|
||||
</>
|
||||
) : (
|
||||
<p>{t('private_key_not_present_good')}</p>
|
||||
)}
|
||||
</div>
|
||||
{myPrivHex ? <p>{t('private_key_warning')}</p> : ''}
|
||||
|
||||
{Helpers.isStandalone() ? (
|
||||
<>
|
||||
<h3>{t('delete_account')}</h3>
|
||||
<p>
|
||||
<button className="btn btn-sm btn-danger" onClick={() => this.deleteAccount()}>
|
||||
{t('delete_account')}
|
||||
</button>
|
||||
</p>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default Account;
|
||||
|
@ -1,86 +1,79 @@
|
||||
import Component from '../../BaseComponent';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import localState from '../../LocalState';
|
||||
import { translate as t } from '../../translations/Translation.mjs';
|
||||
|
||||
export default class Content extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
settings: {},
|
||||
};
|
||||
}
|
||||
export default function Content() {
|
||||
const [settings, setSettings] = useState({});
|
||||
|
||||
componentDidMount() {
|
||||
localState.get('settings').on(this.inject());
|
||||
}
|
||||
useEffect(() => {
|
||||
const unsubscribe = localState.get('settings').on((newSettings) => {
|
||||
setSettings(newSettings);
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, []);
|
||||
|
||||
render() {
|
||||
const noteSettings = [
|
||||
// { setting: 'enableMarkdown', label: 'Markdown' },
|
||||
{ setting: 'loadReactions', label: 'Replies and reactions' },
|
||||
{ setting: 'showLikes', label: 'Likes' },
|
||||
{ setting: 'showZaps', label: 'Zaps' },
|
||||
{ setting: 'showReposts', label: 'Reposts' },
|
||||
];
|
||||
// TODO get these from the embeds themselves
|
||||
const mediaSettings = [
|
||||
{ setting: 'enableImages', label: 'Images' },
|
||||
{ setting: 'enableAudio', label: 'Audio' },
|
||||
{ setting: 'enableVideo', label: 'Videos' },
|
||||
{ setting: 'autoplayVideos', label: 'Autoplay videos' },
|
||||
{ setting: 'enableAppleMusic', label: 'Apple Music' },
|
||||
{ setting: 'enableInstagram', label: 'Instagram' },
|
||||
{ setting: 'enableSoundCloud', label: 'SoundCloud' },
|
||||
{ setting: 'enableSpotify', label: 'Spotify' },
|
||||
{ setting: 'enableTidal', label: 'Tidal' },
|
||||
{ setting: 'enableTiktok', label: 'TikTok' },
|
||||
{ setting: 'enableTwitch', label: 'Twitch' },
|
||||
{ setting: 'enableTwitter', label: 'Twitter' },
|
||||
{ setting: 'enableYoutube', label: 'YouTube' },
|
||||
{ setting: 'enableWavlake', label: 'Wavlake' },
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<div class="centered-container">
|
||||
<h2>{t('content')}</h2>
|
||||
const noteSettings = [
|
||||
// { setting: 'enableMarkdown', label: 'Markdown' },
|
||||
{ setting: 'loadReactions', label: 'Replies and reactions' },
|
||||
{ setting: 'showLikes', label: 'Likes' },
|
||||
{ setting: 'showZaps', label: 'Zaps' },
|
||||
{ setting: 'showReposts', label: 'Reposts' },
|
||||
];
|
||||
|
||||
<h3>{t('notes')}</h3>
|
||||
{noteSettings.map(({ setting, label }) => (
|
||||
<p key={setting}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={this.state.settings[setting] !== false}
|
||||
onChange={() =>
|
||||
localState
|
||||
.get('settings')
|
||||
.get(setting)
|
||||
.put(!(this.state.settings[setting] !== false))
|
||||
}
|
||||
id={setting}
|
||||
/>
|
||||
<label htmlFor={setting}> {label}</label>
|
||||
</p>
|
||||
))}
|
||||
const mediaSettings = [
|
||||
{ setting: 'enableImages', label: 'Images' },
|
||||
{ setting: 'enableAudio', label: 'Audio' },
|
||||
{ setting: 'enableVideo', label: 'Videos' },
|
||||
{ setting: 'autoplayVideos', label: 'Autoplay videos' },
|
||||
{ setting: 'enableAppleMusic', label: 'Apple Music' },
|
||||
{ setting: 'enableInstagram', label: 'Instagram' },
|
||||
{ setting: 'enableSoundCloud', label: 'SoundCloud' },
|
||||
{ setting: 'enableSpotify', label: 'Spotify' },
|
||||
{ setting: 'enableTidal', label: 'Tidal' },
|
||||
{ setting: 'enableTiktok', label: 'TikTok' },
|
||||
{ setting: 'enableTwitch', label: 'Twitch' },
|
||||
{ setting: 'enableTwitter', label: 'Twitter' },
|
||||
{ setting: 'enableYoutube', label: 'YouTube' },
|
||||
{ setting: 'enableWavlake', label: 'Wavlake' },
|
||||
];
|
||||
|
||||
<h3>{t('media')}</h3>
|
||||
{mediaSettings.map(({ setting, label }) => (
|
||||
<p key={setting}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={this.state.settings[setting] !== false}
|
||||
onChange={() =>
|
||||
localState
|
||||
.get('settings')
|
||||
.get(setting)
|
||||
.put(!(this.state.settings[setting] !== false))
|
||||
}
|
||||
id={setting}
|
||||
/>
|
||||
<label htmlFor={setting}> {label}</label>
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
const handleChange = (setting) => {
|
||||
localState
|
||||
.get('settings')
|
||||
.get(setting)
|
||||
.put(!(settings[setting] !== false));
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="centered-container">
|
||||
<h2>{t('content')}</h2>
|
||||
|
||||
<h3>{t('notes')}</h3>
|
||||
{noteSettings.map(({ setting, label }) => (
|
||||
<p key={setting}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={settings[setting] !== false}
|
||||
onChange={() => handleChange(setting)}
|
||||
id={setting}
|
||||
/>
|
||||
<label htmlFor={setting}> {label}</label>
|
||||
</p>
|
||||
))}
|
||||
|
||||
<h3>{t('media')}</h3>
|
||||
{mediaSettings.map(({ setting, label }) => (
|
||||
<p key={setting}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={settings[setting] !== false}
|
||||
onChange={() => handleChange(setting)}
|
||||
id={setting}
|
||||
/>
|
||||
<label htmlFor={setting}> {label}</label>
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,61 +1,58 @@
|
||||
import Component from '../../BaseComponent';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import localState from '../../LocalState';
|
||||
export default class DevSettings extends Component {
|
||||
render() {
|
||||
const renderCheckbox = (key, label, defaultValue) => (
|
||||
<p>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={key}
|
||||
checked={this.state[key] === undefined ? defaultValue : this.state[key]}
|
||||
onChange={(e) => {
|
||||
const checked = (e.target as HTMLInputElement).checked;
|
||||
localState.get('dev').get(key).put(checked);
|
||||
}}
|
||||
/>
|
||||
<label htmlFor={key}>{label}</label>
|
||||
</p>
|
||||
);
|
||||
|
||||
// TODO reset button
|
||||
export default function DevSettings() {
|
||||
const [state, setState] = useState({});
|
||||
|
||||
const checkboxes = [
|
||||
{
|
||||
key: 'logSubscriptions',
|
||||
label: 'Log RelayPool subscriptions',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
key: 'indexedDbSave',
|
||||
label: 'Save events to IndexedDB',
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
key: 'indexedDbLoad',
|
||||
label: 'Load events from IndexedDB',
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
key: 'askEventsFromRelays',
|
||||
label: 'Ask events from relays',
|
||||
defaultValue: true,
|
||||
},
|
||||
];
|
||||
useEffect(() => {
|
||||
const unsubscribe = localState.get('dev').on((data) => setState(data));
|
||||
return () => unsubscribe();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="centered-container">
|
||||
<h3>Developer</h3>
|
||||
<p>Settings intended for Iris developers.</p>
|
||||
{checkboxes.map(({ key, label, defaultValue }) =>
|
||||
renderCheckbox(key, label, defaultValue),
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
const renderCheckbox = (key, label, defaultValue) => (
|
||||
<p>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={key}
|
||||
checked={state[key] === undefined ? defaultValue : state[key]}
|
||||
onChange={(e) => {
|
||||
const checked = (e.target as HTMLInputElement).checked;
|
||||
localState.get('dev').get(key).put(checked);
|
||||
}}
|
||||
/>
|
||||
<label htmlFor={key}>{label}</label>
|
||||
</p>
|
||||
);
|
||||
|
||||
componentDidMount() {
|
||||
localState.get('dev').on(this.sub((data) => this.setState(data)));
|
||||
}
|
||||
const checkboxes = [
|
||||
{
|
||||
key: 'logSubscriptions',
|
||||
label: 'Log RelayPool subscriptions',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
key: 'indexedDbSave',
|
||||
label: 'Save events to IndexedDB',
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
key: 'indexedDbLoad',
|
||||
label: 'Load events from IndexedDB',
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
key: 'askEventsFromRelays',
|
||||
label: 'Ask events from relays',
|
||||
defaultValue: true,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div class="centered-container">
|
||||
<h3>Developer</h3>
|
||||
<p>Settings intended for Iris developers.</p>
|
||||
{checkboxes.map(({ key, label, defaultValue }) => renderCheckbox(key, label, defaultValue))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
import Component from '../../BaseComponent';
|
||||
|
||||
import Account from './Account.js';
|
||||
import Appearance from './Appearance';
|
||||
import Backup from './Backup';
|
||||
import Content from './Content';
|
||||
import ContentPage from './Content';
|
||||
import Dev from './Dev';
|
||||
import IrisAccount from './IrisAccount.js';
|
||||
import Language from './Language';
|
||||
@ -11,14 +9,13 @@ import Network from './Network.js';
|
||||
import Payments from './Payments';
|
||||
import SocialNetwork from './SocialNetwork';
|
||||
|
||||
export default class SettingsContent extends Component {
|
||||
content = '';
|
||||
pages = {
|
||||
const SettingsContent = (props) => {
|
||||
const pages = {
|
||||
account: Account,
|
||||
network: Network,
|
||||
appearance: Appearance,
|
||||
language: Language,
|
||||
content: Content,
|
||||
content: ContentPage,
|
||||
payments: Payments,
|
||||
backup: Backup,
|
||||
social_network: SocialNetwork,
|
||||
@ -26,16 +23,13 @@ export default class SettingsContent extends Component {
|
||||
dev: Dev,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.content = 'home';
|
||||
}
|
||||
render() {
|
||||
const Content = this.pages[this.props.id] || this.pages.account;
|
||||
return (
|
||||
<div className="prose">
|
||||
<Content />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
const SelectedContent = pages[props.id] || pages.account;
|
||||
|
||||
return (
|
||||
<div className="prose">
|
||||
<SelectedContent />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsContent;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { LanguageIcon } from '@heroicons/react/24/solid';
|
||||
import { route } from 'preact-router';
|
||||
|
||||
import Component from '../../BaseComponent';
|
||||
import Show from '../../components/helpers/Show';
|
||||
import localState from '../../LocalState';
|
||||
import { translate as t } from '../../translations/Translation.mjs';
|
||||
@ -22,41 +21,42 @@ if (['iris.to', 'beta.iris.to', 'localhost'].includes(window.location.hostname))
|
||||
SETTINGS.iris_account = 'iris.to';
|
||||
}
|
||||
|
||||
export default class SettingsMenu extends Component {
|
||||
menuLinkClicked(url, e) {
|
||||
const SettingsMenu = (props) => {
|
||||
const activePage = props.activePage || 'account';
|
||||
|
||||
const menuLinkClicked = (url, e) => {
|
||||
e.preventDefault();
|
||||
localState.get('toggleSettingsMenu').put(false);
|
||||
localState.get('scrollUp').put(true);
|
||||
route(`/settings/${url}`);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const activePage = this.props.activePage || 'account';
|
||||
return (
|
||||
<div
|
||||
className={`flex-col w-full md:w-48 flex-shrink-0 ${
|
||||
!this.props.activePage ? 'flex' : 'hidden md:flex'
|
||||
}`}
|
||||
>
|
||||
{Object.keys(SETTINGS).map((page) => {
|
||||
if (!SETTINGS[page]) return;
|
||||
return (
|
||||
<a
|
||||
href="#"
|
||||
className={`btn inline-flex w-auto flex items-center p-3 rounded-full transition-colors duration-200 hover:bg-neutral-900 ${
|
||||
activePage === page && window.innerWidth > 640 ? 'active' : ''
|
||||
}`}
|
||||
onClick={(e) => this.menuLinkClicked(page, e)}
|
||||
key={page}
|
||||
>
|
||||
<span class="text">{t(SETTINGS[page])}</span>
|
||||
<Show when={page === 'language'}>
|
||||
<LanguageIcon width={16} />
|
||||
</Show>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={`flex-col w-full md:w-48 flex-shrink-0 ${
|
||||
!props.activePage ? 'flex' : 'hidden md:flex'
|
||||
}`}
|
||||
>
|
||||
{Object.keys(SETTINGS).map((page) => {
|
||||
if (!SETTINGS[page]) return null;
|
||||
return (
|
||||
<a
|
||||
href="#"
|
||||
className={`btn inline-flex w-auto flex items-center p-3 rounded-full transition-colors duration-200 hover:bg-neutral-900 ${
|
||||
activePage === page && window.innerWidth > 640 ? 'active' : ''
|
||||
}`}
|
||||
onClick={(e) => menuLinkClicked(page, e)}
|
||||
key={page}
|
||||
>
|
||||
<span class="text">{t(SETTINGS[page])}</span>
|
||||
<Show when={page === 'language'}>
|
||||
<LanguageIcon width={16} />
|
||||
</Show>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsMenu;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import { Link } from 'preact-router';
|
||||
|
||||
import Component from '../../BaseComponent';
|
||||
import Name from '../../components/user/Name';
|
||||
import localState from '../../LocalState';
|
||||
import Events from '../../nostr/Events';
|
||||
@ -9,109 +8,96 @@ import Key from '../../nostr/Key';
|
||||
import SocialNetwork from '../../nostr/SocialNetwork';
|
||||
import { translate as t } from '../../translations/Translation.mjs';
|
||||
|
||||
export default class SocialNetworkSettings extends Component {
|
||||
private refreshInterval: any;
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
blockedUsers: [] as string[],
|
||||
globalFilter: {},
|
||||
};
|
||||
}
|
||||
render() {
|
||||
let hasBlockedUsers = false;
|
||||
const blockedUsers = this.state.blockedUsers.map((user) => {
|
||||
const bech32 = Key.toNostrBech32Address(user, 'npub');
|
||||
if (bech32) {
|
||||
hasBlockedUsers = true;
|
||||
return (
|
||||
<div key={user}>
|
||||
<Link href={`/${bech32}`}>
|
||||
<Name pub={user} />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
const SocialNetworkSettings = () => {
|
||||
const [blockedUsers, setBlockedUsers] = useState<string[]>([]);
|
||||
const [globalFilter, setGlobalFilter] = useState<any>({});
|
||||
const [showBlockedUsers, setShowBlockedUsers] = useState(false);
|
||||
let refreshInterval: any;
|
||||
|
||||
const followDistances = Array.from(SocialNetwork.usersByFollowDistance.entries()).slice(1);
|
||||
const handleFilterChange = (e) => {
|
||||
const value = parseInt(e.target?.value);
|
||||
localState.get('globalFilter').get('maxFollowDistance').put(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="centered-container">
|
||||
<h2>{t('social_network')}</h2>
|
||||
<h3>Stored on your device</h3>
|
||||
<p>Total size: {followDistances.reduce((a, b) => a + b[1].size, 0)} users</p>
|
||||
<p>Depth: {followDistances.length} degrees of separation</p>
|
||||
{followDistances.sort().map((distance) => (
|
||||
<div>
|
||||
{distance[0] || t('unknown')}: {distance[1].size} users
|
||||
</div>
|
||||
))}
|
||||
<p>Filter incoming events by follow distance:</p>
|
||||
<select
|
||||
className="select"
|
||||
value={this.state.globalFilter.maxFollowDistance}
|
||||
onChange={(e) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
localState.get('globalFilter').get('maxFollowDistance').put(parseInt(target.value));
|
||||
}}
|
||||
>
|
||||
<option value="0">Off</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
</select>
|
||||
<p>Minimum number of followers at maximum follow distance:</p>
|
||||
<input
|
||||
className="input"
|
||||
type="number"
|
||||
value={
|
||||
this.state.globalFilter.minFollowersAtMaxDistance ||
|
||||
Events.DEFAULT_GLOBAL_FILTER.minFollowersAtMaxDistance
|
||||
}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
localState
|
||||
.get('globalFilter')
|
||||
.get('minFollowersAtMaxDistance')
|
||||
.put(parseInt(target.value));
|
||||
}}
|
||||
/>
|
||||
<h3>{t('blocked_users')}</h3>
|
||||
{!hasBlockedUsers && t('none')}
|
||||
<p>
|
||||
<a
|
||||
href=""
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
this.setState({
|
||||
showBlockedUsers: !this.state.showBlockedUsers,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{this.state.showBlockedUsers ? t('hide') : t('show')} {blockedUsers.length}
|
||||
</a>
|
||||
</p>
|
||||
{this.state.showBlockedUsers && blockedUsers}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
componentDidMount() {
|
||||
SocialNetwork.getBlockedUsers((blockedUsers) => {
|
||||
this.setState({ blockedUsers: Array.from(blockedUsers) });
|
||||
});
|
||||
localState.get('globalFilter').on(this.inject());
|
||||
this.refreshInterval = setInterval(() => {
|
||||
this.forceUpdate();
|
||||
const handleMinFollowersChange = (e) => {
|
||||
const value = parseInt(e.target?.value);
|
||||
localState.get('globalFilter').get('minFollowersAtMaxDistance').put(value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
SocialNetwork.getBlockedUsers((set) => setBlockedUsers(Array.from(set)));
|
||||
localState.get('globalFilter').on(setGlobalFilter);
|
||||
refreshInterval = setInterval(() => {
|
||||
// We might need some actual updating logic here
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.refreshInterval);
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
clearInterval(refreshInterval);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const hasBlockedUsers = blockedUsers.some((user) => Key.toNostrBech32Address(user, 'npub'));
|
||||
const renderedBlockedUsers = blockedUsers.map((user) => {
|
||||
const bech32 = Key.toNostrBech32Address(user, 'npub');
|
||||
if (bech32) {
|
||||
return (
|
||||
<div key={user}>
|
||||
<Link href={`/${bech32}`}>
|
||||
<Name pub={user} />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const followDistances = Array.from(SocialNetwork.usersByFollowDistance.entries()).slice(1);
|
||||
|
||||
return (
|
||||
<div class="centered-container">
|
||||
<h2>{t('social_network')}</h2>
|
||||
<h3>Stored on your device</h3>
|
||||
<p>Total size: {followDistances.reduce((a, b) => a + b[1].size, 0)} users</p>
|
||||
<p>Depth: {followDistances.length} degrees of separation</p>
|
||||
{followDistances.sort().map((distance) => (
|
||||
<div>
|
||||
{distance[0] || t('unknown')}: {distance[1].size} users
|
||||
</div>
|
||||
))}
|
||||
<p>Filter incoming events by follow distance:</p>
|
||||
<select
|
||||
className="select"
|
||||
value={globalFilter.maxFollowDistance}
|
||||
onChange={handleFilterChange}
|
||||
>
|
||||
{/* ... options here ... */}
|
||||
</select>
|
||||
<p>Minimum number of followers at maximum follow distance:</p>
|
||||
<input
|
||||
className="input"
|
||||
type="number"
|
||||
value={
|
||||
globalFilter.minFollowersAtMaxDistance ||
|
||||
Events.DEFAULT_GLOBAL_FILTER.minFollowersAtMaxDistance
|
||||
}
|
||||
onChange={handleMinFollowersChange}
|
||||
/>
|
||||
<h3>{t('blocked_users')}</h3>
|
||||
{!hasBlockedUsers && t('none')}
|
||||
<p>
|
||||
<a
|
||||
href=""
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setShowBlockedUsers(!showBlockedUsers);
|
||||
}}
|
||||
>
|
||||
{showBlockedUsers ? t('hide') : t('show')} {renderedBlockedUsers.length}
|
||||
</a>
|
||||
</p>
|
||||
{showBlockedUsers && renderedBlockedUsers}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SocialNetworkSettings;
|
||||
|
Loading…
Reference in New Issue
Block a user