language selector & settings fixes

This commit is contained in:
Martti Malmi 2021-08-11 13:42:04 +03:00
parent d170b6c31b
commit 027d7bad7f
8 changed files with 337 additions and 314 deletions

View File

@ -1900,6 +1900,10 @@ form.public {
background-color: var(--chat-hover);
}
.language-selector {
margin-left: 5px;
}
.explorer-row {
border-radius: 3px;
padding: 3px;

View File

@ -112,4 +112,5 @@ export default {
</svg>
`,
menu: html`<svg fill="currentColor" x="0px" y="0px" viewBox="0 0 384 384" width="24px" height="24px" enable-background="new 0 0 384 384;"><g><rect x="0" y="277.333" width="384" height="42.667"/><rect x="0" y="170.667" width="384" height="42.667"/><rect x="0" y="64" width="384" height="42.667"/></g></svg>`,
language: html`<svg width="14" height="14" style="margin-bottom: -1px" x="0px" y="0px" viewBox="0 0 469.333 469.333" style="enable-background:new 0 0 469.333 469.333;" xml:space="preserve"><path fill="currentColor" d="M253.227,300.267L253.227,300.267L199.04,246.72l0.64-0.64c37.12-41.387,63.573-88.96,79.147-139.307h62.507V64H192 V21.333h-42.667V64H0v42.453h238.293c-14.4,41.173-36.907,80.213-67.627,114.347c-19.84-22.08-36.267-46.08-49.28-71.467H78.72 c15.573,34.773,36.907,67.627,63.573,97.28l-108.48,107.2L64,384l106.667-106.667l66.347,66.347L253.227,300.267z"/><path fill="currentColor" d="M373.333,192h-42.667l-96,256h42.667l24-64h101.333l24,64h42.667L373.333,192z M317.333,341.333L352,248.853 l34.667,92.48H317.333z"/></svg>`,
};

View File

@ -8,7 +8,7 @@ import QRScanner from './QRScanner.js';
import PeerManager from './PeerManager.js';
import Session from './Session.js';
import Settings from './views/Settings.js';
import Settings from './views/settings/Settings.js';
import LogoutConfirmation from './views/LogoutConfirmation.js';
import Chat from './views/Chat.js';
import Store from './views/Store.js';

View File

@ -2,6 +2,7 @@ import { html } from 'htm/preact';
import {AVAILABLE_LANGUAGES, language} from '../Translation.js';
import Translations from '../Translations.js';
import $ from 'jquery';
import Icons from '../Icons.js';
function onLanguageChange(e) {
const l = $(e.target).val();
@ -12,6 +13,7 @@ function onLanguageChange(e) {
}
const LanguageSelector = () => html`
${Icons.language}
<select class="language-selector" onChange=${e => onLanguageChange(e)} value=${language}>${
AVAILABLE_LANGUAGES.map(l =>
html`<option value=${l}>${Translations[l].language_name}</option>`

View File

@ -88,7 +88,6 @@ class Login extends Component {
<br/>
<p><a href="#" id="show-existing-account-login" onClick=${() => this.setState({showSwitchAccount: true})}>${t('already_have_an_account')}</a></p>
<p>
<svg width="14" height="14" style="margin-bottom: -1px" x="0px" y="0px" viewBox="0 0 469.333 469.333" style="enable-background:new 0 0 469.333 469.333;" xml:space="preserve"><path fill="currentColor" d="M253.227,300.267L253.227,300.267L199.04,246.72l0.64-0.64c37.12-41.387,63.573-88.96,79.147-139.307h62.507V64H192 V21.333h-42.667V64H0v42.453h238.293c-14.4,41.173-36.907,80.213-67.627,114.347c-19.84-22.08-36.267-46.08-49.28-71.467H78.72 c15.573,34.773,36.907,67.627,63.573,97.28l-108.48,107.2L64,384l106.667-106.667l66.347,66.347L253.227,300.267z"/><path fill="currentColor" d="M373.333,192h-42.667l-96,256h42.667l24-64h101.333l24,64h42.667L373.333,192z M317.333,341.333L352,248.853 l34.667,92.48H317.333z"/></svg>
<${LanguageSelector}/>
</p>
</div>

View File

@ -1,312 +0,0 @@
import Helpers from '../Helpers.js';
import { html } from 'htm/preact';
import State from '../State.js';
import Session from '../Session.js';
import LanguageSelector from '../components/LanguageSelector.js';
import {translate as t} from '../Translation.js';
import PeerManager from '../PeerManager.js';
import {setRTCConfig, getRTCConfig, DEFAULT_RTC_CONFIG} from '../components/VideoCall.js';
import CopyButton from '../components/CopyButton.js';
import View from './View.js';
import { route } from 'preact-router';
import {ExistingAccountLogin} from './Login.js';
import Notifications from '../Notifications.js';
import $ from 'jquery';
import Icons from '../Icons.js';
import _ from 'lodash';
import QRCode from '../lib/qrcode.min';
class Settings extends View {
constructor() {
super();
this.eventListeners = {};
this.state = Session.DEFAULT_SETTINGS;
this.state.webPushSubscriptions = {};
this.state.blockedUsers = {};
this.id = "settings";
}
onProfilePhotoSet(src) {
State.public.user().get('profile').get('photo').put(src);
}
mailtoSubmit(e) {
e.preventDefault();
if (this.state.email && this.state.email === this.state.retypeEmail) {
window.location.href = `mailto:${this.state.email}?&subject=Iris%20private%20key&body=${JSON.stringify(Session.getKey())}`;
}
}
renderView() {
const blockedUsers = _.filter(Object.keys(this.state.blockedUsers), user => this.state.blockedUsers[user]);
return html`
<div class="centered-container">
<h3>${t('account')}</h3>
<p>
<b>${t('save_backup_of_privkey_first')}</b> ${t('otherwise_cant_log_in_again')}
</p>
<p>
<button onClick=${() => route('/logout')}>${t('log_out')}</button>
</p>
<p>
<button onClick=${() => this.setState({showSwitchAccount: !this.state.showSwitchAccount})}>${t('switch_account')}</button>
</p>
${this.state.showSwitchAccount ? html`<${ExistingAccountLogin}/>` : ''}
<h4>${t('private_key')}</h4>
<p dangerouslySetInnerHTML=${{ __html: t('private_key_warning') }} ></p>
<p>
<button onClick=${() => downloadKey()}>${t('download_private_key')}</button>
<${CopyButton} notShareable=${true} text=${t('copy_private_key')} copyStr=${JSON.stringify(Session.getKey())}/>
</p>
<p>
<button onClick=${e => togglePrivateKeyQR(e)}>${t('show_privkey_qr')}</button>
</p>
<div id="private-key-qr" class="qr-container"></div>
<p>
${t('email_privkey_to_yourself')}:
</p>
<p>
<form onSubmit=${e => this.mailtoSubmit(e)}>
<input name="email" type="email" onChange=${e => this.setState({email:e.target.value.trim()})} placeholder=${t('email')}/>
<input name="verify_email" type="email" onChange=${e => this.setState({retypeEmail:e.target.value.trim()})} placeholder=${t('retype_email')}/>
<button type="submit">${t('go')}</button>
</form>
</p>
<p><small dangerouslySetInnerHTML=${{ __html: t('privkey_storage_recommendation')}}></small></p>
<hr/>
<h3>${t('language')}</h3>
<p><${LanguageSelector}/></p>
<hr/>
<h3>${t('notifications')}</h3>
<p>${t('web_push_subscriptions')}</p>
<div class="flex-table">
${Object.keys(this.state.webPushSubscriptions).map(k => {
const v = this.state.webPushSubscriptions[k];
return html`
<div class="flex-row">
<div class="flex-cell">${v.endpoint}</div>
<div class="flex-cell no-flex">
<button onClick=${() => Notifications.removeSubscription(k)}>${t('remove')}</button>
</div>
</div>
`;
})}
</div>
<hr/>
<h3>${t('peers')}</h3>
${this.renderPeerList()}
<p><input type="checkbox" checked=${this.state.local.enablePublicPeerDiscovery} onChange=${() => State.local.get('settings').get('enablePublicPeerDiscovery').put(!this.state.local.enablePublicPeerDiscovery)} id="enablePublicPeerDiscovery"/><label for="enablePublicPeerDiscovery">${t('enable_public_peer_discovery')}</label></p>
<h4>${t('maximum_number_of_peer_connections')}</h4>
<p>
<small>${t('there_is_a_bug')}</small>
</p>
<p>
<input type="number" value=${this.state.local.maxConnectedPeers} onChange=${e => State.local.get('settings').get('maxConnectedPeers').put(e.target.value || 0)}/>
</p>
${Helpers.isElectron ? html`
<h4>${t('your_public_address')}</h4>
<p>http://${this.state.electron.publicIp || '-'}:8767/gun</p>
<p><small>If you're behind NAT (likely) and want to accept incoming connections, you need to configure your router to forward the port 8767 to this computer.</small></p>
`: ''}
<h4>${t('set_up_your_own_peer')}</h4>
<p>
<small dangerouslySetInnerHTML=${{ __html: t('peers_info', "href=\"https://github.com/amark/gun#deploy\"")}}></small>
</p>
<p><a href="https://heroku.com/deploy?template=https://github.com/amark/gun">
${Icons.herokuButton}
</a></p>
<p>${t('also')} <a href="https://github.com/amark/gun#docker">Docker</a> ${t('or_small')} <a href="https://github.com/irislib/iris-electron">Iris-electron</a>.</p>
${Helpers.isElectron ? html`
<hr/>
<h3>Desktop</h3>
<p><input type="checkbox" checked=${this.state.electron.openAtLogin} onChange=${() => State.electron.get('settings').get('openAtLogin').put(!this.state.electron.openAtLogin)} id="openAtLogin"/><label for="openAtLogin">Open at login</label></p>
<p><input type="checkbox" checked=${this.state.electron.minimizeOnClose} onChange=${() => State.electron.get('settings').get('minimizeOnClose').put(!this.state.electron.minimizeOnClose)} id="minimizeOnClose"/><label for="minimizeOnClose">Minimize on close</label></p>
`: ''}
<hr/>
<h3>${t('webtorrent')}</h3>
<p><input type="checkbox" checked=${this.state.local.enableWebtorrent} onChange=${() => State.local.get('settings').get('enableWebtorrent').put(!this.state.local.enableWebtorrent)} id="enableWebtorrent"/><label for="enableWebtorrent">${t('automatically_load_webtorrent_attachments')}</label></p>
<p><input type="checkbox" checked=${this.state.local.autoplayWebtorrent} onChange=${() => State.local.get('settings').get('autoplayWebtorrent').put(!this.state.local.autoplayWebtorrent)} id="autoplayWebtorrent"/><label for="autoplayWebtorrent">${t('autoplay_webtorrent_videos')}</label></p>
<hr/>
<h3>${t('webrtc_connection_options')}</h3>
<p><small>${t('webrtc_info')}</small></p>
<p><textarea rows="4" id="rtc-config" placeholder="${t('webrtc_connection_options')}" onChange=${() => this.rtcConfigChanged()}></textarea></p>
<button onClick=${() => this.restoreDefaultRtcConfig()}>${t('restore_defaults')}</button>
<hr/>
<h3>${t('blocked_users')}</h3>
${blockedUsers.map(user => {
if (this.state.blockedUsers[user]) {
return html`<p><a href="/profile/${encodeURIComponent(user)}"><iris-text user=${user} path="profile/name" placeholder="User"/></a></p>`;
}
})}
${blockedUsers.length === 0 ? t('none') : ''}
</div>
`;
}
rtcConfigChanged() {
setRTCConfig(JSON.parse($('#rtc-config').val()));
}
resetPeersClicked() {
PeerManager.resetPeers();
this.setState({});
}
removePeerClicked(url, peerFromGun) {
PeerManager.removePeer(url);
peerFromGun && PeerManager.disconnectPeer(peerFromGun);
}
enablePeerClicked(url, peerFromGun, peer) {
peer.enabled ? PeerManager.disablePeer(url,peerFromGun) : PeerManager.connectPeer(url);
}
renderPeerList() {
let urls = Object.keys(PeerManager.getKnownPeers());
if (this.state.peersFromGun) {
Object.keys(this.state.peersFromGun).forEach(url => urls.indexOf(url) === -1 && urls.push(url));
}
return html`
<div id="peers" class="flex-table">
${urls.length === 0 ? html`
<button id="reset-peers" style="margin-bottom: 15px" onClick=${() => this.resetPeersClicked()}>${t('restore_defaults')}</button>
`: ''}
${urls.map(url => {
if (url == 1) {return;} // weirdness
const peer = PeerManager.getKnownPeers()[url] || {};
const peerFromGun = this.state.peersFromGun && this.state.peersFromGun[url];
const connected = peerFromGun && peerFromGun.wire && peerFromGun.wire.hied === 'hi';
return html`
<div class="flex-row peer">
<div class="flex-cell">
${connected ? html`
<span class="tooltip" style="color: var(--positive-color);margin-right:15px">
<span class="tooltiptext">Connected</span>
<svg height="14" width="14" x="0px" y="0px" viewBox="0 0 191.667 191.667"><path fill="currentColor" d="M95.833,0C42.991,0,0,42.99,0,95.833s42.991,95.834,95.833,95.834s95.833-42.991,95.833-95.834S148.676,0,95.833,0z M150.862,79.646l-60.207,60.207c-2.56,2.56-5.963,3.969-9.583,3.969c-3.62,0-7.023-1.409-9.583-3.969l-30.685-30.685 c-2.56-2.56-3.97-5.963-3.97-9.583c0-3.621,1.41-7.024,3.97-9.584c2.559-2.56,5.962-3.97,9.583-3.97c3.62,0,7.024,1.41,9.583,3.971 l21.101,21.1l50.623-50.623c2.56-2.56,5.963-3.969,9.583-3.969c3.62,0,7.023,1.409,9.583,3.969 C156.146,65.765,156.146,74.362,150.862,79.646z"/></svg>
</span>
` : html`
<small class="tooltip" style="margin-right:15px">
<span class="tooltiptext">Disconnected</span>
<svg width="14" height="14" x="0px" y="0px" viewBox="0 0 512 512" fill="currentColor"><path d="M257,0C116.39,0,0,114.39,0,255s116.39,257,257,257s255-116.39,255-257S397.61,0,257,0z M383.22,338.79 c11.7,11.7,11.7,30.73,0,42.44c-11.61,11.6-30.64,11.79-42.44,0L257,297.42l-85.79,83.82c-11.7,11.7-30.73,11.7-42.44,0 c-11.7-11.7-11.7-30.73,0-42.44l83.8-83.8l-83.8-83.8c-11.7-11.71-11.7-30.74,0-42.44c11.71-11.7,30.74-11.7,42.44,0L257,212.58 l83.78-83.82c11.68-11.68,30.71-11.72,42.44,0c11.7,11.7,11.7,30.73,0,42.44l-83.8,83.8L383.22,338.79z"/></svg>
</small>
`}
${url}
${peer.from ? html`
<br/><small style="cursor:pointer" onClick=${() => route(`/profile/${ peer.from}`)}>${t('from')} ${Helpers.truncateString(peer.from, 10)}</small>
` : ''}
</div>
<div class="flex-cell no-flex">
<button onClick=${() => this.removePeerClicked(url, peerFromGun)}>${t('remove')}</button>
<button onClick=${() => this.enablePeerClicked(url, peerFromGun, peer)}>${peer.enabled ? t('disable') : t('enable')}</button>
</div>
</div>
`;
})
}
<div class="flex-row" id="add-peer-row">
<div class="flex-cell">
<input type="url" id="add-peer-url" placeholder="${t('peer_url')}"/>
<input type="checkbox" id="add-peer-public"/>
<label for="add-peer-public">${t('public')}</label>
<button id="add-peer-btn" onClick=${() => this.addPeerClicked()}>${t('add')}</button>
</div>
</div>
<p>
<small dangerouslySetInnerHTML=${{ __html:t('public_peer_info') }}></small>
</p>
</div>
`;
}
updatePeersFromGun() {
const peersFromGun = State.public.back('opt.peers') || {};
this.setState({peersFromGun});
}
addPeerClicked() {
let url = $('#add-peer-url').val();
let visibility = $('#add-peer-public').is(':checked') ? 'public' : undefined;
PeerManager.addPeer({url, visibility});
$('#add-peer-url').val('');
}
restoreDefaultRtcConfig() {
setRTCConfig(DEFAULT_RTC_CONFIG);
$('#rtc-config').val(JSON.stringify(getRTCConfig()));
}
componentDidMount() {
const blockedUsers = {};
this.updatePeersFromGun();
this.updatePeersFromGunInterval = setInterval(() => this.updatePeersFromGun(), 2000);
$('#rtc-config').val(JSON.stringify(getRTCConfig()));
State.electron && State.electron.get('settings').on(electron => {
this.setState({electron});
});
State.local.get('settings').on(local => {
this.setState({local})
});
State.public.user().get('webPushSubscriptions').map().on(() => this.setState({webPushSubscriptions: Notifications.webPushSubscriptions}));
State.public.user().get('block').map().on((v,k,x,e) => {
this.eventListeners['block'] = e;
blockedUsers[k] = v;
this.setState({blockedUsers});
});
}
componentWillUnmount() {
Object.values(this.eventListeners).forEach(e => e.off());
clearInterval(this.updatePeersFromGunInterval);
}
}
function togglePrivateKeyQR(e) {
let btn = $(e.target);
let show = $('#private-key-qr img').length === 0;
let SHOW_TEXT = t('show_privkey_qr');
let hidePrivateKeyInterval;
function reset() {
clearInterval(hidePrivateKeyInterval);
$('#private-key-qr').empty();
btn.text(SHOW_TEXT);
}
function hideText(s) { return `${t('hide_privkey_qr') } (${ s })`; }
if (show) {
let showPrivateKeySecondsRemaining = 20;
btn.text(hideText(showPrivateKeySecondsRemaining));
hidePrivateKeyInterval = setInterval(() => {
if ($('#private-key-qr img').length === 0) {
clearInterval(hidePrivateKeyInterval);return;
}
showPrivateKeySecondsRemaining -= 1;
if (showPrivateKeySecondsRemaining === 0) {
reset();
} else {
btn.text(hideText(showPrivateKeySecondsRemaining));
}
}, 1000);
let qrCodeEl = $('#private-key-qr');
new QRCode(qrCodeEl[0], {
text: JSON.stringify(Session.getKey()),
width: 300,
height: 300,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRCode.CorrectLevel.H
});
} else {
reset();
}
}
function downloadKey() {
const key = Session.getKey();
delete key['#'];
return Helpers.download('iris_private_key.txt', JSON.stringify(key), 'text/plain', 'utf-8');
}
export default Settings;

View File

@ -0,0 +1,137 @@
import Component from "../../BaseComponent";
import Session from "../../Session";
import State from "../../State";
import {html} from "htm/preact";
import {translate as t} from "../../Translation";
import Helpers from "../../Helpers";
import Icons from "../../Icons";
import PeerManager from "../../PeerManager";
import {route} from "preact-router";
import $ from "jquery";
export default class PeerSettings extends Component {
state = Session.DEFAULT_SETTINGS;
componentDidMount() {
State.local.get('settings').on(this.inject('local'));
this.updatePeersFromGun();
this.updatePeersFromGunInterval = setInterval(() => this.updatePeersFromGun(), 2000);
}
componentWillUnmount() {
super.componentWillUnmount();
clearInterval(this.updatePeersFromGunInterval);
}
render() {
return html`
<h3>${t('peers')}</h3>
${this.renderPeerList()}
<p><input type="checkbox" checked=${this.state.local.enablePublicPeerDiscovery} onChange=${() => State.local.get('settings').get('enablePublicPeerDiscovery').put(!this.state.local.enablePublicPeerDiscovery)} id="enablePublicPeerDiscovery"/><label for="enablePublicPeerDiscovery">${t('enable_public_peer_discovery')}</label></p>
<h4>${t('maximum_number_of_peer_connections')}</h4>
<p>
<small>${t('there_is_a_bug')}</small>
</p>
<p>
<input type="number" value=${this.state.local.maxConnectedPeers} onChange=${e => State.local.get('settings').get('maxConnectedPeers').put(e.target.value || 0)}/>
</p>
${Helpers.isElectron ? html`
<h4>${t('your_public_address')}</h4>
<p>http://${this.state.electron.publicIp || '-'}:8767/gun</p>
<p><small>If you're behind NAT (likely) and want to accept incoming connections, you need to configure your router to forward the port 8767 to this computer.</small></p>
`: ''}
<h4>${t('set_up_your_own_peer')}</h4>
<p>
<small dangerouslySetInnerHTML=${{ __html: t('peers_info', "href=\"https://github.com/amark/gun#deploy\"")}}></small>
</p>
<p><a href="https://heroku.com/deploy?template=https://github.com/amark/gun">
${Icons.herokuButton}
</a></p>
`;
}
resetPeersClicked() {
PeerManager.resetPeers();
this.setState({});
}
removePeerClicked(url, peerFromGun) {
PeerManager.removePeer(url);
peerFromGun && PeerManager.disconnectPeer(peerFromGun);
}
enablePeerClicked(url, peerFromGun, peer) {
peer.enabled ? PeerManager.disablePeer(url,peerFromGun) : PeerManager.connectPeer(url);
}
renderPeerList() {
let urls = Object.keys(PeerManager.getKnownPeers());
if (this.state.peersFromGun) {
Object.keys(this.state.peersFromGun).forEach(url => urls.indexOf(url) === -1 && urls.push(url));
}
return html`
<div id="peers" class="flex-table">
${urls.length === 0 ? html`
<button id="reset-peers" style="margin-bottom: 15px" onClick=${() => this.resetPeersClicked()}>${t('restore_defaults')}</button>
`: ''}
${urls.map(url => {
if (url == 1) {return;} // weirdness
const peer = PeerManager.getKnownPeers()[url] || {};
const peerFromGun = this.state.peersFromGun && this.state.peersFromGun[url];
const connected = peerFromGun && peerFromGun.wire && peerFromGun.wire.hied === 'hi';
return html`
<div class="flex-row peer">
<div class="flex-cell">
${connected ? html`
<span class="tooltip" style="color: var(--positive-color);margin-right:15px">
<span class="tooltiptext">Connected</span>
<svg height="14" width="14" x="0px" y="0px" viewBox="0 0 191.667 191.667"><path fill="currentColor" d="M95.833,0C42.991,0,0,42.99,0,95.833s42.991,95.834,95.833,95.834s95.833-42.991,95.833-95.834S148.676,0,95.833,0z M150.862,79.646l-60.207,60.207c-2.56,2.56-5.963,3.969-9.583,3.969c-3.62,0-7.023-1.409-9.583-3.969l-30.685-30.685 c-2.56-2.56-3.97-5.963-3.97-9.583c0-3.621,1.41-7.024,3.97-9.584c2.559-2.56,5.962-3.97,9.583-3.97c3.62,0,7.024,1.41,9.583,3.971 l21.101,21.1l50.623-50.623c2.56-2.56,5.963-3.969,9.583-3.969c3.62,0,7.023,1.409,9.583,3.969 C156.146,65.765,156.146,74.362,150.862,79.646z"/></svg>
</span>
` : html`
<small class="tooltip" style="margin-right:15px">
<span class="tooltiptext">Disconnected</span>
<svg width="14" height="14" x="0px" y="0px" viewBox="0 0 512 512" fill="currentColor"><path d="M257,0C116.39,0,0,114.39,0,255s116.39,257,257,257s255-116.39,255-257S397.61,0,257,0z M383.22,338.79 c11.7,11.7,11.7,30.73,0,42.44c-11.61,11.6-30.64,11.79-42.44,0L257,297.42l-85.79,83.82c-11.7,11.7-30.73,11.7-42.44,0 c-11.7-11.7-11.7-30.73,0-42.44l83.8-83.8l-83.8-83.8c-11.7-11.71-11.7-30.74,0-42.44c11.71-11.7,30.74-11.7,42.44,0L257,212.58 l83.78-83.82c11.68-11.68,30.71-11.72,42.44,0c11.7,11.7,11.7,30.73,0,42.44l-83.8,83.8L383.22,338.79z"/></svg>
</small>
`}
${url}
${peer.from ? html`
<br/><small style="cursor:pointer" onClick=${() => route(`/profile/${ peer.from}`)}>${t('from')} ${Helpers.truncateString(peer.from, 10)}</small>
` : ''}
</div>
<div class="flex-cell no-flex">
<button onClick=${() => this.removePeerClicked(url, peerFromGun)}>${t('remove')}</button>
<button onClick=${() => this.enablePeerClicked(url, peerFromGun, peer)}>${peer.enabled ? t('disable') : t('enable')}</button>
</div>
</div>
`;
})
}
<div class="flex-row" id="add-peer-row">
<div class="flex-cell">
<input type="url" id="add-peer-url" placeholder="${t('peer_url')}"/>
<input type="checkbox" id="add-peer-public"/>
<label for="add-peer-public">${t('public')}</label>
<button id="add-peer-btn" onClick=${() => this.addPeerClicked()}>${t('add')}</button>
</div>
</div>
<p>
<small dangerouslySetInnerHTML=${{ __html:t('public_peer_info') }}></small>
</p>
</div>
`;
}
updatePeersFromGun() {
const peersFromGun = State.public.back('opt.peers') || {};
this.setState({peersFromGun});
}
addPeerClicked() {
let url = $('#add-peer-url').val();
let visibility = $('#add-peer-public').is(':checked') ? 'public' : undefined;
PeerManager.addPeer({url, visibility});
$('#add-peer-url').val('');
}
}

View File

@ -0,0 +1,192 @@
import Helpers from '../../Helpers.js';
import { html } from 'htm/preact';
import State from '../../State.js';
import Session from '../../Session.js';
import LanguageSelector from '../../components/LanguageSelector.js';
import {translate as t} from '../../Translation.js';
import {setRTCConfig, getRTCConfig, DEFAULT_RTC_CONFIG} from '../../components/VideoCall.js';
import CopyButton from '../../components/CopyButton.js';
import View from '../View.js';
import { route } from 'preact-router';
import {ExistingAccountLogin} from '../Login.js';
import Notifications from '../../Notifications.js';
import PeerSettings from './PeerSettings';
import $ from 'jquery';
import _ from 'lodash';
import QRCode from '../../lib/qrcode.min';
class Settings extends View {
constructor() {
super();
this.state = Session.DEFAULT_SETTINGS;
this.state.webPushSubscriptions = {};
this.state.blockedUsers = {};
this.id = "settings";
}
mailtoSubmit(e) {
e.preventDefault();
if (this.state.email && this.state.email === this.state.retypeEmail) {
window.location.href = `mailto:${this.state.email}?&subject=Iris%20private%20key&body=${JSON.stringify(Session.getKey())}`;
}
}
renderView() {
const blockedUsers = _.filter(Object.keys(this.state.blockedUsers), user => this.state.blockedUsers[user]);
return html`
<div class="centered-container">
<h3>${t('account')}</h3>
<p>
<b>${t('save_backup_of_privkey_first')}</b> ${t('otherwise_cant_log_in_again')}
</p>
<p>
<button onClick=${() => route('/logout')}>${t('log_out')}</button>
</p>
<p>
<button onClick=${() => this.setState({showSwitchAccount: !this.state.showSwitchAccount})}>${t('switch_account')}</button>
</p>
${this.state.showSwitchAccount ? html`<${ExistingAccountLogin}/>` : ''}
<h4>${t('private_key')}</h4>
<p dangerouslySetInnerHTML=${{ __html: t('private_key_warning') }} ></p>
<p>
<button onClick=${() => downloadKey()}>${t('download_private_key')}</button>
<${CopyButton} notShareable=${true} text=${t('copy_private_key')} copyStr=${JSON.stringify(Session.getKey())}/>
</p>
<p>
<button onClick=${e => togglePrivateKeyQR(e)}>${t('show_privkey_qr')}</button>
</p>
<div id="private-key-qr" class="qr-container"></div>
<p>
${t('email_privkey_to_yourself')}:
</p>
<p>
<form onSubmit=${e => this.mailtoSubmit(e)}>
<input name="email" type="email" onChange=${e => this.setState({email:e.target.value.trim()})} placeholder=${t('email')}/>
<input name="verify_email" type="email" onChange=${e => this.setState({retypeEmail:e.target.value.trim()})} placeholder=${t('retype_email')}/>
<button type="submit">${t('go')}</button>
</form>
</p>
<p><small dangerouslySetInnerHTML=${{ __html: t('privkey_storage_recommendation')}}></small></p>
<hr/>
<h3>${t('language')}</h3>
<p><${LanguageSelector} key="moi"/></p>
<hr/>
<h3>${t('notifications')}</h3>
<p>${t('web_push_subscriptions')}</p>
<div class="flex-table">
${Object.keys(this.state.webPushSubscriptions).map(k => {
const v = this.state.webPushSubscriptions[k];
return html`
<div class="flex-row">
<div class="flex-cell">${v.endpoint}</div>
<div class="flex-cell no-flex">
<button onClick=${() => Notifications.removeSubscription(k)}>${t('remove')}</button>
</div>
</div>
`;
})}
</div>
<hr/>
<${PeerSettings}/>
<p>${t('also')} <a href="https://github.com/amark/gun#docker">Docker</a> ${t('or_small')} <a href="https://github.com/irislib/iris-electron">Iris-electron</a>.</p>
${Helpers.isElectron ? html`
<hr/>
<h3>Desktop</h3>
<p><input type="checkbox" checked=${this.state.electron.openAtLogin} onChange=${() => State.electron.get('settings').get('openAtLogin').put(!this.state.electron.openAtLogin)} id="openAtLogin"/><label for="openAtLogin">Open at login</label></p>
<p><input type="checkbox" checked=${this.state.electron.minimizeOnClose} onChange=${() => State.electron.get('settings').get('minimizeOnClose').put(!this.state.electron.minimizeOnClose)} id="minimizeOnClose"/><label for="minimizeOnClose">Minimize on close</label></p>
`: ''}
<hr/>
<h3>${t('webtorrent')}</h3>
<p><input type="checkbox" checked=${this.state.local.enableWebtorrent} onChange=${() => State.local.get('settings').get('enableWebtorrent').put(!this.state.local.enableWebtorrent)} id="enableWebtorrent"/><label for="enableWebtorrent">${t('automatically_load_webtorrent_attachments')}</label></p>
<p><input type="checkbox" checked=${this.state.local.autoplayWebtorrent} onChange=${() => State.local.get('settings').get('autoplayWebtorrent').put(!this.state.local.autoplayWebtorrent)} id="autoplayWebtorrent"/><label for="autoplayWebtorrent">${t('autoplay_webtorrent_videos')}</label></p>
<hr/>
<h3>${t('webrtc_connection_options')}</h3>
<p><small>${t('webrtc_info')}</small></p>
<p><textarea rows="4" id="rtc-config" placeholder="${t('webrtc_connection_options')}" onChange=${e => this.rtcConfigChanged(e)}></textarea></p>
<button onClick=${() => this.restoreDefaultRtcConfig()}>${t('restore_defaults')}</button>
<hr/>
<h3>${t('blocked_users')}</h3>
${blockedUsers.map(user => {
if (this.state.blockedUsers[user]) {
return html`<p><a href="/profile/${encodeURIComponent(user)}"><iris-text user=${user} path="profile/name" placeholder="User"/></a></p>`;
}
})}
${blockedUsers.length === 0 ? t('none') : ''}
</div>
`;
}
rtcConfigChanged(e) {
setRTCConfig(JSON.parse(e.target.value));
}
restoreDefaultRtcConfig() {
setRTCConfig(DEFAULT_RTC_CONFIG);
$('#rtc-config').val(JSON.stringify(getRTCConfig()));
}
componentDidMount() {
const blockedUsers = {};
$('#rtc-config').val(JSON.stringify(getRTCConfig()));
State.electron && State.electron.get('settings').on(this.inject('electron', 'electron'));
State.local.get('settings').on(this.inject('local', 'local'));
State.public.user().get('webPushSubscriptions').map().on(this.sub(
() => this.setState({webPushSubscriptions: Notifications.webPushSubscriptions})
));
State.public.user().get('block').map().on(this.sub(
(v,k) => {
blockedUsers[k] = v;
this.setState({blockedUsers});
}
));
}
}
function togglePrivateKeyQR(e) {
let btn = $(e.target);
let show = $('#private-key-qr img').length === 0;
let SHOW_TEXT = t('show_privkey_qr');
let hidePrivateKeyInterval;
function reset() {
clearInterval(hidePrivateKeyInterval);
$('#private-key-qr').empty();
btn.text(SHOW_TEXT);
}
function hideText(s) { return `${t('hide_privkey_qr') } (${ s })`; }
if (show) {
let showPrivateKeySecondsRemaining = 20;
btn.text(hideText(showPrivateKeySecondsRemaining));
hidePrivateKeyInterval = setInterval(() => {
if ($('#private-key-qr img').length === 0) {
clearInterval(hidePrivateKeyInterval);return;
}
showPrivateKeySecondsRemaining -= 1;
if (showPrivateKeySecondsRemaining === 0) {
reset();
} else {
btn.text(hideText(showPrivateKeySecondsRemaining));
}
}, 1000);
let qrCodeEl = $('#private-key-qr');
new QRCode(qrCodeEl[0], {
text: JSON.stringify(Session.getKey()),
width: 300,
height: 300,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRCode.CorrectLevel.H
});
} else {
reset();
}
}
function downloadKey() {
const key = Session.getKey();
delete key['#'];
return Helpers.download('iris_private_key.txt', JSON.stringify(key), 'text/plain', 'utf-8');
}
export default Settings;