Merge pull request #740 from vincentbavitz/brand-redesign

Various changes from redesign checklist
This commit is contained in:
Vince 2020-01-29 08:41:54 +10:00 committed by GitHub
commit 2f42a19987
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 769 additions and 514 deletions

View file

@ -774,7 +774,7 @@
"description": "Shown to separate the types of search results"
},
"messagesHeader": {
"message": "Messages",
"message": "Sessions",
"description": "Shown to separate the types of search results"
},
"settingsHeader": {
@ -961,6 +961,9 @@
"close": {
"message": "Close"
},
"continue": {
"message": "Continue"
},
"pairNewDevice": {
"message": "Pair New Device"
},
@ -1070,7 +1073,7 @@
"description": "Label for the time a message was received"
},
"sendMessage": {
"message": "Send a message",
"message": " Type your message",
"description": "Placeholder text in the message entry field"
},
"secondaryDeviceDefaultFR": {
@ -1179,7 +1182,8 @@
"description": "Warning for account deletion in settings view"
},
"deleteAccountWarningSub": {
"message": "This is completely irreversible and will leave no trace.",
"message":
"Delete all history, including all messages, sessions, and contacts. Once deleted, these cannot be restored.",
"description": "Warning for account deletion in settings view"
},
"deleteContact": {
@ -1352,7 +1356,8 @@
"Option to control creation and send of link previews in setting screen"
},
"linkPreviewDescription": {
"message": "Enable link previews",
"message":
"Link previews supported for Imgur, Instagram, Pinterest, Reddit, and YouTube.",
"description": "Description shown for the Link Preview option "
},
"mediaPermissionsTitle": {
@ -1371,7 +1376,7 @@
"description": "Description of the media permission description"
},
"clearDataHeader": {
"message": "Clear Data",
"message": "Clear All Local Data",
"description":
"Header in the settings dialog for the section dealing with data deletion"
},
@ -1439,17 +1444,17 @@
"description": "Label for disabling notifications"
},
"nameAndMessage": {
"message": "Both sender name and message",
"message": "Name and content",
"description":
"Label for setting notifications to display name and message text"
},
"noNameOrMessage": {
"message": "Neither name nor message",
"message": "No name or content",
"description":
"Label for setting notifications to display no name and no message text"
},
"nameOnly": {
"message": "Only sender name",
"message": "Name Only",
"description": "Label for setting notifications to display sender name only"
},
"newMessage": {
@ -2129,6 +2134,10 @@
"message": "Copied public key",
"description": "A toast message telling the user that the key was copied"
},
"copiedChatId": {
"message": "Copied chat ID",
"description": "A toast message telling the user that the key was copied"
},
"copyMessage": {
"message": "Copy message text",
"description":
@ -2167,12 +2176,16 @@
"yourSessionID": {
"message": "Your Session ID"
},
"setStatus": {
"message": "Set a status..."
},
"setAccountPasswordTitle": {
"message": "Set Account Password",
"description": "Prompt for user to set account password in settings view"
},
"setAccountPasswordDescription": {
"message": "Secure your account and public key with a password",
"message":
"Require password to unlock Sessions screen.You can still receive message notifications while Screen Lock is enabled. Loki Messengers notification settings allow you to customize information that is displayed",
"description": "Description for set account password setting view"
},
"changeAccountPasswordTitle": {
@ -2268,7 +2281,7 @@
},
"passwordViewTitle": {
"message": "Type in your password",
"message": "Type In Your Password",
"description":
"The title shown when user needs to type in a password to unlock the messenger"
},
@ -2296,6 +2309,9 @@
"message": "Remove Password",
"description": "Button action that the user can click to remove a password"
},
"maxPasswordAttempts": {
"message": "Invalid Password. Would you like to reset the database?"
},
"typeInOldPassword": {
"message": "Please type in your old password"
},
@ -2303,7 +2319,7 @@
"message": "Old password is invalid"
},
"invalidPassword": {
"message": "Invalid password"
"message": "Invalid Password"
},
"noGivenPassword": {
"message": "Please enter your password"
@ -2476,54 +2492,57 @@
"message": "Sign In"
},
"yourUniqueSessionID": {
"message": "Your Unique Session ID"
"message": "Say hello to your Session ID"
},
"allUsersAreRandomly...": {
"message":
"All users are randomly generated a set of numbers that act as their unique Session ID. Share your Session ID in order to chat with your friends!"
"Your Session ID is the unique address people can use to contact you on Session. Your Session ID is totally private, anonymous, and has no connection to your real identity."
},
"getStarted": {
"message": "Get started"
},
"generateSessionID": {
"message": "Generate Session ID"
"message": "Create Session ID"
},
"mnemonicSeed": {
"message": "Mnemonic Seed"
},
"enterSeed": {
"message": "Enter Seed"
"message": "Enter Recovery Phrase"
},
"displayName": {
"message": "Display Name"
},
"enterDisplayName": {
"message": "Enter Display Name / Alias"
"message": "Enter a display name"
},
"optionalPassword": {
"message": "Optional Password"
"message": "Verify Password"
},
"enterOptionalPassword": {
"message": "Enter Optional Password"
"message": "Enter password (optional)"
},
"verifyPassword": {
"message": "Verify Password"
},
"devicePairingHeader": {
"message":
"Open the Session Messenger App on your primary device and select Device Pairing from the main menu. Then, enter your Session ID below to sign in."
"Open Session on your other device and navigate to the Linked Devices section in your user account screen. Select Link a Device to prepare your other device for pairing, then enter your Session ID below to link this device to your Session ID."
},
"enterSessionIDHere": {
"message": "Enter your Session ID here"
"message": "Enter other devices Session ID here"
},
"continueYourSession": {
"message": "Continue Your Session"
"message": "Link Device"
},
"restoreSessionID": {
"message": "Restore Session ID"
},
"restoreUsingSeed": {
"message": "Restore Using Seed"
"message": "Restore From Recovery Phrase"
},
"linkDeviceToExistingAccount": {
"message": "Link Device To Existing Account"
"message": "Link Device to Existing Session ID"
},
"or": {
"message": "or"
@ -2536,13 +2555,13 @@
"message": "Begin<br />your<br />Session."
},
"welcomeToYourSession": {
"message": "Welcome to your Session!"
"message": "Welcome to your Session"
},
"completeSignUp": {
"message": "Complete Sign Up"
},
"compose": {
"message": "Compose"
"message": "New Session"
},
"searchForAKeyPhrase": {
"message": "Search for a key phrase or contact"
@ -2554,11 +2573,11 @@
"message": "Enter Session ID"
},
"pasteSessionIDRecipient": {
"message": "Paste Session ID of recipient"
"message": "Enter a Session ID"
},
"usersCanShareTheir...": {
"message":
"Users can share their Session ID by going into their account settings and clicking \"Share Public Key\"."
"Users can share their Session ID from their account settings, or by sharing their QR code."
},
"searchByIDOrDisplayName": {
"message": "Search by ID # or Display Name"
@ -2579,7 +2598,7 @@
"message": "Create Group"
},
"yourPublicKey": {
"message": "Your Public Key"
"message": "Your Session ID"
},
"accept": {
"message": "Accept"
@ -2644,21 +2663,23 @@
"channels": {
"message": "Channels"
},
"groups": {
"message": "Groups"
},
"addChannel": {
"message": "Add channel"
"message": "Join Open Group"
},
"enterChannelURL": {
"message": "Enter Channel URL"
"message": "Enter Open Group URL"
},
"channelUrlPlaceholder": {
"message": "https://yourchannel.lokinet.org"
"message": "https://chat.lokinet.org"
},
"addChannelDescription": {
"message":
"Enter the URL of the public channel you'd like to join in the format above."
"message": "Enter an open group URL."
},
"joinChannel": {
"message": "Join Channel"
"message": "Join Open Group"
},
"next": {
"message": "Next"

View file

@ -192,42 +192,6 @@
</div>
</script>
<script type='text/x-tmpl-mustache' id='beta-disclaimer-dialog'>
<div class="content">
<div class="betaDisclaimerView" style="display: none;">
<h2>
Thanks for testing Session Messenger!
</h2>
<p>
Thanks for testing Session Messenger! This software is a beta version of the full Session Messenger software suite, and so is missing some of the features the full version will have.
</p>
<p>
<b>
This version of Session Messenger provides no guarantees of metadata privacy.
</b>
</p>
<p>
While your messages are secured using end to end encryption, in this beta version of Loki messenger, <b>third parties (like your ISP or the Service Node network) can see who youre talking to</b> and when youre sending or receiving messages.
</p>
<p>
It is also possible that <b>third parties could correlate your public key to your IP address</b> and your real identity if they learn your public key.
</p>
<p>
However, no one except you and your intended recipients will be able to see the contents of your messages. We recommend using existing methods, like Tor or I2P to mask your IP address while using Session Messenger beta version.
</p>
<p>
As a beta, this software is still experimental. When things aren't working for you, or you feel confused by the app, please let us know by filing an issue on <a href="https://github.com/loki-project/loki-messenger">Github</a> or making suggestions on <a href="https://discordapp.com/invite/67GXfD6">Discord</a>.
</p>
<button class='ok' tabindex='1'>{{ ok }}</button>
</div>
</div>
</script>
<script type='text/x-tmpl-mustache' id='connecting-to-server-template'>
<div class="content">
{{ #title }}

View file

@ -4,7 +4,7 @@
<meta charset='utf-8'>
<meta content='width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0' name='viewport'>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Signal</title>
<title>Session</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href='/images/loki/loki_icon_128.png' rel='shortcut icon'>

View file

@ -823,7 +823,7 @@
window.showQRDialog = window.owsDesktopApp.appView.showQRDialog;
window.showSeedDialog = window.owsDesktopApp.appView.showSeedDialog;
window.showPasswordDialog = window.owsDesktopApp.appView.showPasswordDialog;
window.showEditProfileDialog = async () => {
window.showEditProfileDialog = async callback => {
const ourNumber = window.storage.get('primaryDevicePubKey');
const conversation = await ConversationController.getOrCreateAndWait(
ourNumber,
@ -852,6 +852,7 @@
if (appView) {
appView.showEditProfileDialog({
callback,
profileName: displayName,
pubkey: ourNumber,
avatarPath,
@ -972,6 +973,9 @@
return toastID;
};
// Get memberlist. This function is not accurate >>
// window.getMemberList = window.lokiPublicChatAPI.getListOfMembers();
window.deleteAccount = async () => {
try {
window.log.info('Deleting everything!');

View file

@ -257,6 +257,9 @@
const messageSelected = this.selectedMessages.size > 0;
if (messageSelected) {
// Hide ellipses icon
$('.title-wrapper .session-icon.ellipses').css({ opacity: 0 });
$('.messages li, .messages > div').addClass('shadowed');
$('.message-selection-overlay').addClass('overlay');
$('.module-conversation-header').addClass('overlayed');
@ -268,6 +271,9 @@
$(`#${messageId}`).removeClass('shadowed');
}
} else {
// Hide ellipses icon
$('.title-wrapper .session-icon.ellipses').css({ opacity: 1 });
$('.messages li, .messages > div').removeClass('shadowed');
$('.message-selection-overlay').removeClass('overlay');
$('.module-conversation-header').removeClass('overlayed');
@ -2680,8 +2686,13 @@
copyPublicKey() {
clipboard.writeText(this.id);
const isGroup = this.getProps().type === 'group';
const copiedMessage = isGroup
? i18n('copiedChatId')
: i18n('copiedPublicKey');
window.pushToast({
title: i18n('copiedPublicKey'),
title: copiedMessage,
type: 'success',
id: 'copiedPublicKey',
});

View file

@ -74,6 +74,9 @@ const {
const {
SessionPasswordModal,
} = require('../../ts/components/session/SessionPasswordModal');
const {
SessionPasswordPrompt,
} = require('../../ts/components/session/SessionPasswordPrompt');
const {
SessionConfirm,
@ -82,6 +85,9 @@ const {
const {
SessionDropdown,
} = require('../../ts/components/session/SessionDropdown');
const {
SessionScrollButton,
} = require('../../ts/components/session/SessionScrollButton');
const {
SessionRegistrationView,
} = require('../../ts/components/session/SessionRegistrationView');
@ -290,7 +296,9 @@ exports.setup = (options = {}) => {
SessionQRModal,
SessionSeedModal,
SessionPasswordModal,
SessionPasswordPrompt,
SessionDropdown,
SessionScrollButton,
MediaGallery,
Message,
MessageBody,

View file

@ -1,4 +1,4 @@
/* global Backbone, Whisper, storage, _, ConversationController, $ */
/* global Backbone, i18n, Whisper, storage, _, ConversationController, $ */
/* eslint-disable more/no-then */
@ -238,8 +238,19 @@
this.el.append(dialog.el);
},
showLeaveGroupDialog(groupConvo) {
const dialog = new Whisper.LeaveGroupDialogView(groupConvo);
this.el.append(dialog.el);
const title = groupConvo.isPublic()
? i18n('deletePublicChannel')
: i18n('deleteContact');
const message = groupConvo.isPublic()
? i18n('deletePublicChannelConfirmation')
: i18n('deleteContactConfirmation');
window.confirmationDialog({
title,
message,
resolve: () => ConversationController.deleteContact(groupConvo.id),
});
},
showInviteFriendsDialog(groupConvo) {
const dialog = new Whisper.InviteFriendsDialogView(groupConvo);

View file

@ -1,72 +0,0 @@
/* global i18n: false */
/* global Whisper: false */
/* eslint-disable no-new */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
const { Logs } = window.Signal;
const CLEAR_DATA_STEPS = {
CHOICE: 1,
DELETING: 2,
};
window.Whisper.ClearDataView = Whisper.View.extend({
templateName: 'clear-data',
className: 'full-screen-flow overlay',
events: {
'click .cancel': 'onCancel',
'click .delete-all-data': 'onDeleteAllData',
},
initialize(onClear = null) {
this.step = CLEAR_DATA_STEPS.CHOICE;
this.onClear = onClear;
},
onCancel() {
this.remove();
},
async onDeleteAllData() {
window.log.info('Deleting everything!');
this.step = CLEAR_DATA_STEPS.DELETING;
this.render();
await this.clearAllData();
},
async clearAllData() {
if (this.onClear) {
this.onClear();
} else {
try {
await Logs.deleteAll();
await window.Signal.Data.removeAll();
await window.Signal.Data.close();
await window.Signal.Data.removeDB();
await window.Signal.Data.removeOtherData();
} catch (error) {
window.log.error(
'Something went wrong deleting all data:',
error && error.stack ? error.stack : error
);
}
window.restart();
}
},
render_attributes() {
return {
isStep1: this.step === CLEAR_DATA_STEPS.CHOICE,
header: i18n('deleteAllDataHeader'),
body: i18n('deleteAllDataBody'),
cancelButton: i18n('cancel'),
deleteButton: i18n('deleteAllDataButton'),
isStep2: this.step === CLEAR_DATA_STEPS.DELETING,
deleting: i18n('deleteAllDataProgress'),
};
},
});
})();

View file

@ -47,47 +47,6 @@
},
});
Whisper.LeaveGroupDialogView = Whisper.View.extend({
className: 'loki-dialog modal',
initialize(groupConvo) {
this.groupConvo = groupConvo;
this.titleText = groupConvo.get('name');
this.messageText = i18n('leaveGroupDialogTitle');
this.okText = i18n('yes');
this.cancelText = i18n('cancel');
this.close = this.close.bind(this);
this.confirm = this.confirm.bind(this);
this.$el.focus();
this.render();
},
render() {
this.dialogView = new Whisper.ReactWrapperView({
className: 'leave-group-dialog',
Component: window.Signal.Components.ConfirmDialog,
props: {
titleText: this.titleText,
messageText: this.messageText,
okText: this.okText,
cancelText: this.cancelText,
onConfirm: this.confirm,
onClose: this.close,
},
});
this.$el.append(this.dialogView.el);
return this;
},
async confirm() {
await this.groupConvo.leaveGroup();
this.close();
},
close() {
this.remove();
},
});
Whisper.UpdateGroupDialogView = Whisper.View.extend({
className: 'loki-dialog modal',
initialize(groupConvo) {

View file

@ -8,9 +8,17 @@
Whisper.EditProfileDialogView = Whisper.View.extend({
className: 'loki-dialog modal',
initialize({ profileName, avatarPath, avatarColor, pubkey, onOk }) {
initialize({
profileName,
avatarPath,
avatarColor,
pubkey,
onOk,
callback,
}) {
this.close = this.close.bind(this);
this.callback = callback;
this.profileName = profileName;
this.pubkey = pubkey;
this.avatarPath = avatarPath;
@ -25,6 +33,7 @@
className: 'edit-profile-dialog',
Component: window.Signal.Components.EditProfileDialog,
props: {
callback: this.callback,
onOk: this.onOk,
onClose: this.close,
profileName: this.profileName,

View file

@ -123,7 +123,8 @@
});
if (!window.storage.get('betaReleaseDisclaimerAccepted')) {
this.showBetaReleaseDisclaimer();
// Beta disclaimer disabled.
// this.showBetaReleaseDisclaimer();
}
if (!options.initialLoadComplete) {

View file

@ -1,7 +1,4 @@
/* global i18n: false */
/* global Whisper: false */
/* eslint-disable no-new */
/* global Whisper */
// eslint-disable-next-line func-names
(function() {
@ -9,63 +6,20 @@
window.Whisper = window.Whisper || {};
const MIN_LOGIN_TRIES = 3;
Whisper.PasswordView = Whisper.View.extend({
className: 'password full-screen-flow standalone-fullscreen',
templateName: 'password',
events: {
keyup: 'onKeyup',
'click #unlock-button': 'onLogin',
'click #reset-button': 'onReset',
},
initialize() {
this.errorCount = 0;
this.render();
},
render_attributes() {
return {
title: i18n('passwordViewTitle'),
buttonText: i18n('unlock'),
resetText: i18n('resetDatabase'),
showReset: this.errorCount >= MIN_LOGIN_TRIES,
};
},
onKeyup(event) {
switch (event.key) {
case 'Enter':
this.onLogin();
break;
default:
return;
}
event.preventDefault();
},
async onLogin() {
const passPhrase = this.$('#passPhrase').val();
const trimmed = passPhrase ? passPhrase.trim() : passPhrase;
this.setError('');
try {
await window.onLogin(trimmed);
} catch (e) {
// Increment the error counter and show the button if necessary
this.errorCount += 1;
if (this.errorCount >= MIN_LOGIN_TRIES) {
this.render();
}
this.setError(`Error: ${e}`);
}
},
setError(string) {
this.$('.error').text(string);
},
onReset() {
const clearDataView = new window.Whisper.ClearDataView(() => {
window.resetDatabase();
render() {
this.passwordView = new window.Whisper.ReactWrapperView({
className: 'password overlay',
Component: window.Signal.Components.SessionPasswordPrompt,
props: {},
});
clearDataView.render();
this.$el.append(clearDataView.el);
this.$el.append(this.passwordView.el);
return this;
},
});
})();

View file

@ -1,4 +1,4 @@
/* global Whisper, i18n */
/* global Whisper */
// eslint-disable-next-line func-names
(function() {
@ -7,9 +7,6 @@
window.Whisper = window.Whisper || {};
Whisper.ScrollDownButtonView = Whisper.View.extend({
className: 'module-scroll-down',
templateName: 'scroll-down-button-view',
initialize(options = {}) {
this.count = options.count || 0;
},
@ -19,21 +16,17 @@
this.render();
},
render_attributes() {
const buttonClass =
this.count > 0 ? 'module-scroll-down__button--new-messages' : '';
render() {
this.scrollButtonView = new Whisper.ReactWrapperView({
className: 'module-scroll-down',
Component: window.Signal.Components.SessionScrollButton,
props: {
count: this.count,
},
});
let moreBelow = i18n('scrollDown');
if (this.count > 1) {
moreBelow = i18n('messagesBelow');
} else if (this.count === 1) {
moreBelow = i18n('messageBelow');
}
return {
buttonClass,
moreBelow,
};
this.$el.append(this.scrollButtonView.el);
return this;
},
});
})();

View file

@ -16,63 +16,12 @@
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
<style>
</style>
<script type='text/x-tmpl-mustache' id='password'>
<div class='content-wrapper standalone'>
<div class='content'>
<h2>{{ title }}</h2>
<div class='inputs'>
<input class='form-control' type='password' id='passPhrase' placeholder='Password' autocomplete='off' spellcheck='false' />
<a class='button session-button brand green' id='unlock-button'>{{ buttonText }}</a>
<div class='error'></div>
{{ #showReset }}
<div class='reset'>
<a id='reset-button'>{{ resetText }}</a>
</div>
{{ /showReset }}
</div>
</div>
</div>
</script>
<script type='text/x-tmpl-mustache' id='clear-data'>
{{#isStep1}}
<div class='step'>
<div class='inner'>
<div class='step-body'>
<span class='banner-icon alert-outline-red'></span>
<div class='header'>{{ header }}</div>
<div class='body-text-wide'>{{ body }}</div>
</div>
<div class='nav'>
<div>
<a class='button neutral cancel'>{{ cancelButton }}</a>
<a class='button destructive delete-all-data'>{{ deleteButton }}</a>
</div>
</div>
</div>
</div>
{{/isStep1}}
{{#isStep2}}
<div id='step3' class='step'>
<div class='inner'>
<div class='step-body'>
<span class='banner-icon delete'></span>
<div class='header'>{{ deleting }}</div>
</div>
<div class='progress'>
<div class='bar-container'>
<div class='bar progress-bar progress-bar-striped active'></div>
</div>
</div>
</div>
</div>
{{/isStep2}}
</script>
<script type='text/javascript' src='js/components.js'></script>
<script type='text/javascript' src='js/views/whisper_view.js'></script>
<script type='text/javascript' src='js/views/react_wrapper_view.js'></script>
<script type='text/javascript' src='js/views/password_view.js'></script>
<script type='text/javascript' src='js/views/clear_data_view.js'></script>
</head>
<body>
<div class='app-loading-screen'>

View file

@ -8,6 +8,9 @@ const config = url.parse(window.location.toString(), true).query;
const { locale } = config;
const localeMessages = ipcRenderer.sendSync('locale-data');
window.React = require('react');
window.ReactDOM = require('react-dom');
window.theme = config.theme;
window.i18n = i18n.setup(locale, localeMessages);
@ -17,6 +20,9 @@ window.getAppInstance = () => config.appInstance;
// So far we're only using this for Signal.Types
const Signal = require('./js/modules/signal');
const electron = require('electron');
const ipc = electron.ipcRenderer;
window.Signal = Signal.setup({
Attachments: null,
@ -24,12 +30,32 @@ window.Signal = Signal.setup({
getRegionCode: () => null,
});
window.Signal.Logs = require('./js/modules/logs');
window.CONSTANTS = {
MAX_LOGIN_TRIES: 3,
MAX_PASSWORD_LENGTH: 32,
MAX_USERNAME_LENGTH: 20,
};
window.passwordUtil = require('./app/password_util');
window.Signal.Logs = require('./js/modules/logs');
window.resetDatabase = () => {
window.log.info('reset database');
ipcRenderer.send('resetDatabase');
};
window.restart = () => {
window.log.info('restart');
ipc.send('restart');
};
window.clearLocalData = async () => {
window.resetDatabase();
window.restart();
};
window.onLogin = passPhrase =>
new Promise((resolve, reject) => {
ipcRenderer.once('password-window-login-response', (event, error) => {

View file

@ -59,6 +59,19 @@ window.isBeforeVersion = (toCheck, baseVersion) => {
}
};
window.CONSTANTS = {
MAX_LOGIN_TRIES: 3,
MAX_PASSWORD_LENGTH: 32,
MAX_USERNAME_LENGTH: 20,
};
window.versionInfo = {
environment: window.getEnvironment(),
version: window.getVersion(),
commitHash: window.getCommitHash(),
appInstance: window.getAppInstance(),
};
// temporary clearnet fix
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
window.getSelfSignedCert = () => {
@ -122,6 +135,11 @@ window.restart = () => {
ipc.send('restart');
};
window.resetDatabase = () => {
window.log.info('reset database');
ipc.send('resetDatabase');
};
// Events for updating block number states across different windows.
// In this case we need these to update the blocked number
// collection on the main window from the settings window.
@ -497,7 +515,7 @@ window.SMALL_GROUP_SIZE_LIMIT = 10;
window.lokiFeatureFlags = {
multiDeviceUnpairing: true,
privateGroupChats: false,
privateGroupChats: true,
};
// eslint-disable-next-line no-extend-native,func-names

View file

@ -140,12 +140,12 @@
.name-part {
font-weight: 300;
margin-left: 6px;
margin-left: 12px;
}
.pubkey-part {
margin-left: 6px;
color: darkslategrey;
margin-left: 10px;
opacity: 0.6;
}
}
}

View file

@ -31,6 +31,15 @@
src: url('../fonts/SFProText-Regular.ttf') format('truetype');
}
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
// Session Colors
$session-font-family: 'Wasa';
@ -149,6 +158,7 @@ div.spacer-lg {
}
$session-transition-duration: 0.25s;
$session-fadein-duration: 0.1s;
$session-icon-size-sm: 15px;
$session-icon-size-md: 20px;
@ -215,6 +225,7 @@ $session_message-container-border-radius: 5px;
}
.button-group > div {
display: inline-flex;
margin-left: 5px;
margin-right: 5px;
}
@ -248,6 +259,10 @@ $session_message-container-border-radius: 5px;
&.brand {
color: $session-color-white;
&:hover {
filter: brightness(90%);
}
&.green,
&.white,
&.primary,
@ -524,8 +539,7 @@ label {
.close-button {
float: left;
margin-top: 17px;
margin-left: 7px;
margin: 17px 0px 0px 0px;
}
}
.message-selection-overlay div[role='button'] {
@ -629,6 +643,7 @@ label {
}
.session-modal {
animation: fadein $session-transition-duration;
z-index: 150;
position: absolute;
left: 50%;
@ -1130,6 +1145,31 @@ label {
}
}
&-view {
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
&__version-info {
display: flex;
justify-content: space-between;
padding: $session-margin-sm $session-margin-md;
background-color: $session-shade-5;
font-size: $session-font-xs;
span {
opacity: 0.4;
transition: $session-transition-duration;
&:hover {
opacity: 1;
}
}
}
&__password-lock {
display: flex;
align-items: center;
@ -1172,7 +1212,8 @@ label {
}
}
#qr svg {
#qr svg,
.qr-image svg {
width: $session-modal-size-sm;
height: $session-modal-size-sm;
padding: $session-margin-xs;
@ -1272,7 +1313,6 @@ label {
background-color: $session-color-white;
border-color: $session-color-white;
}
}
.user-details-dialog {
.session-id-editable {
@ -1340,27 +1380,36 @@ input {
}
}
button.module-scroll-down {
&__button {
background-color: $session-shade-6;
}
.module-scroll-down {
animation: fadein $session-fadein-duration;
bottom: 15px;
&__button:hover {
&__icon {
.session-icon-button {
display: flex;
justify-content: center;
align-items: center;
height: 40px;
width: 40px;
border-radius: 50%;
opacity: 1;
background-color: $session-shade-8;
svg path {
transition: $session-transition-duration;
@include color-svg('../images/down.svg', $session-color-white);
opacity: 0.6;
}
}
&__icon {
@include color-svg('../images/down.svg', rgba($session-color-white, 0.6));
&:hover svg path {
opacity: 1;
}
}
}
/* Memberlist */
.member-list-container .member {
&-item {
padding: $session-margin-sm;
font-family: 'SF Pro Text';
padding: $session-margin-sm $session-margin-md;
background-color: $session-shade-5;
&:hover:not(.member-selected) {
@ -1394,3 +1443,89 @@ button.module-scroll-down {
}
}
}
.clear-data,
.password-prompt {
&-wrapper {
display: flex;
justify-content: center;
align-items: center;
background-color: $session-color-black;
width: 100%;
height: 100%;
padding: 3 * $session-margin-lg;
}
&-error-section {
width: 100%;
color: $session-color-white;
margin: -$session-margin-sm 0px 2 * $session-margin-lg 0px;
.session-label {
&.primary {
background-color: rgba($session-color-primary, 0.3);
}
padding: $session-margin-xs $session-margin-sm;
font-size: $session-font-xs;
text-align: center;
}
}
&-container {
font-family: 'SF Pro Text';
color: $session-color-white;
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 600px;
min-width: 420px;
padding: 3 * $session-margin-lg 2 * $session-margin-lg;
box-sizing: border-box;
background-color: $session-shade-4;
border: 1px solid $session-shade-8;
border-radius: 2px;
.warning-info-area,
.password-info-area {
display: inline-flex;
justify-content: center;
align-items: center;
h1 {
color: $session-color-white;
}
svg {
margin-right: $session-margin-lg;
}
}
p,
input {
margin: $session-margin-lg 0px;
}
.button-group {
display: inline-flex;
}
#password-prompt-input {
width: 100%;
color: #fff;
background-color: #2e2e2e;
margin-top: 2 * $session-margin-lg;
padding: $session-margin-xs $session-margin-lg;
outline: none;
border: none;
border-radius: 2px;
text-align: center;
font-size: 24px;
letter-spacing: 5px;
font-family: 'SF Pro Text';
}
}
}

View file

@ -23,6 +23,7 @@ declare global {
}
interface Props {
callback: any;
i18n: any;
profileName: string;
avatarPath: string;
@ -50,6 +51,7 @@ export class EditProfileDialog extends React.Component<Props, State> {
this.onClickOK = this.onClickOK.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
this.onFileSelected = this.onFileSelected.bind(this);
this.fireInputEvent = this.fireInputEvent.bind(this);
this.state = {
profileName: this.props.profileName,
@ -139,33 +141,24 @@ export class EditProfileDialog extends React.Component<Props, State> {
<div
className="image-upload-section"
role="button"
onClick={() => {
const el = this.inputEl.current;
if (el) {
el.click();
}
}}
>
<input
type="file"
ref={this.inputEl}
className="input-file"
placeholder="input file"
name="name"
onChange={this.onFileSelected}
/>
</div>
<div
className="qr-view-button"
role="button"
onClick={() => {
this.setState({ mode: 'qr' });
}}
>
onClick={this.fireInputEvent}
/>
<input
type="file"
ref={this.inputEl}
className="input-file"
placeholder="input file"
name="name"
onChange={this.onFileSelected}
/>
<div className="qr-view-button">
<SessionIconButton
iconType={SessionIconType.QR}
iconSize={SessionIconSize.Small}
iconColor={'#000000'}
onClick={() => {
this.setState({ mode: 'qr' });
}}
/>
</div>
</div>
@ -174,6 +167,15 @@ export class EditProfileDialog extends React.Component<Props, State> {
);
}
private fireInputEvent() {
this.setState({ mode: 'edit' }, () => {
const el = this.inputEl.current;
if (el) {
el.click();
}
});
}
private renderDefaultView() {
return (
<>
@ -206,6 +208,7 @@ export class EditProfileDialog extends React.Component<Props, State> {
value={this.state.profileName}
placeholder={placeholderText}
onChange={this.onNameEdited}
maxLength={window.CONSTANTS.MAX_USERNAME_LENGTH}
tabIndex={0}
required={true}
aria-required={true}
@ -258,10 +261,10 @@ export class EditProfileDialog extends React.Component<Props, State> {
);
}
private onNameEdited(e: any) {
e.persist();
private onNameEdited(event: any) {
event.persist();
const newName = e.target.value.replace(window.displayNameRegex, '');
const newName = event.target.value.replace(window.displayNameRegex, '');
this.setState(state => {
return {
@ -274,7 +277,9 @@ export class EditProfileDialog extends React.Component<Props, State> {
private onKeyUp(event: any) {
switch (event.key) {
case 'Enter':
this.onClickOK();
if (this.state.mode === 'edit') {
this.onClickOK();
}
break;
case 'Esc':
case 'Escape':
@ -297,7 +302,10 @@ export class EditProfileDialog extends React.Component<Props, State> {
private onClickOK() {
const newName = this.state.profileName.trim();
if (newName === '') {
if (
newName.length === 0 ||
newName.length > window.CONSTANTS.MAX_USERNAME_LENGTH
) {
return;
}
@ -311,10 +319,17 @@ export class EditProfileDialog extends React.Component<Props, State> {
this.props.onOk(newName, avatar);
this.setState({
mode: 'default',
setProfileName: this.state.profileName,
});
this.setState(
{
mode: 'default',
setProfileName: this.state.profileName,
},
() => {
// Update settinngs in dialog complete;
// now callback to reloadactions panel avatar
this.props.callback(this.state.avatar);
}
);
}
private closeDialog() {

View file

@ -243,6 +243,7 @@ export class ConversationHeader extends React.Component<Props> {
iconType={SessionIconType.Search}
iconSize={SessionIconSize.Large}
iconPadded={true}
onClick={this.highlightMessageSearch}
/>
</div>
);
@ -377,6 +378,12 @@ export class ConversationHeader extends React.Component<Props> {
}
}
public highlightMessageSearch() {
// This is a temporary fix. In future we want to search
// messages in the current conversation
$('.session-search-input input').focus();
}
private renderMemberCount() {
const memberCount = this.props.members.length;

View file

@ -68,8 +68,6 @@ class MemberItem extends React.Component<MemberItemProps> {
const mark = markType === 'kicked' ? '✘' : '✔';
const nameAndPubKey = `${name} ${window.shortenPubkey(pubkey)}`;
return (
<div
role="button"
@ -80,7 +78,8 @@ class MemberItem extends React.Component<MemberItemProps> {
onClick={this.handleClick}
>
{this.renderAvatar()}
<span className="name-part">{nameAndPubKey}</span>
<span className="name-part">{name}</span>
<span className="pubkey-part">{pubkey}</span>
<span className={classNames(markClasses)}>{mark}</span>
</div>
);

View file

@ -24,92 +24,14 @@ interface Props {
receivedFriendRequestCount: number;
}
const Section = ({
isSelected,
onSelect,
type,
avatarPath,
notificationCount,
}: {
isSelected: boolean;
onSelect?: (event: SectionType) => void;
type: SectionType;
avatarPath?: string;
avatarColor?: string;
notificationCount?: number;
}) => {
const handleClick = onSelect
? () => {
type === SectionType.Profile
? window.showEditProfileDialog()
: /* tslint:disable-next-line:no-void-expression */
onSelect(type);
}
: undefined;
if (type === SectionType.Profile) {
return (
<Avatar
avatarPath={avatarPath}
conversationType="direct"
i18n={window.i18n}
// tslint:disable-next-line: no-backbone-get-set-outside-model
phoneNumber={window.storage.get('primaryDevicePubKey')}
size={28}
onAvatarClick={handleClick}
/>
);
}
let iconType: SessionIconType;
switch (type) {
case SectionType.Message:
iconType = SessionIconType.ChatBubble;
break;
case SectionType.Contact:
iconType = SessionIconType.Users;
break;
case SectionType.Channel:
iconType = SessionIconType.Globe;
break;
case SectionType.Settings:
iconType = SessionIconType.Gear;
break;
case SectionType.Moon:
iconType = SessionIconType.Moon;
break;
default:
iconType = SessionIconType.Moon;
}
if (!isSelected) {
return (
<SessionIconButton
iconSize={SessionIconSize.Medium}
iconType={iconType}
notificationCount={notificationCount}
onClick={handleClick}
/>
);
} else {
return (
<SessionIconButton
iconSize={SessionIconSize.Medium}
iconType={iconType}
notificationCount={notificationCount}
onClick={handleClick}
isSelected={isSelected}
/>
);
}
};
export class ActionsPanel extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
avatarPath: '',
};
this.editProfileHandle = this.editProfileHandle.bind(this);
}
public componentDidMount() {
@ -125,6 +47,95 @@ export class ActionsPanel extends React.Component<Props, State> {
);
}
public Section = ({
isSelected,
onSelect,
type,
avatarPath,
notificationCount,
}: {
isSelected: boolean;
onSelect?: (event: SectionType) => void;
type: SectionType;
avatarPath?: string;
avatarColor?: string;
notificationCount?: number;
}) => {
const handleClick = onSelect
? () => {
type === SectionType.Profile
? /* tslint:disable-next-line:no-void-expression */
this.editProfileHandle()
: /* tslint:disable-next-line:no-void-expression */
onSelect(type);
}
: undefined;
if (type === SectionType.Profile) {
return (
<Avatar
avatarPath={avatarPath}
conversationType="direct"
i18n={window.i18n}
// tslint:disable-next-line: no-backbone-get-set-outside-model
phoneNumber={window.storage.get('primaryDevicePubKey')}
size={28}
onAvatarClick={handleClick}
/>
);
}
let iconType: SessionIconType;
switch (type) {
case SectionType.Message:
iconType = SessionIconType.ChatBubble;
break;
case SectionType.Contact:
iconType = SessionIconType.Users;
break;
case SectionType.Channel:
iconType = SessionIconType.Globe;
break;
case SectionType.Settings:
iconType = SessionIconType.Gear;
break;
case SectionType.Moon:
iconType = SessionIconType.Moon;
break;
default:
iconType = SessionIconType.Moon;
}
if (!isSelected) {
return (
<SessionIconButton
iconSize={SessionIconSize.Medium}
iconType={iconType}
notificationCount={notificationCount}
onClick={handleClick}
/>
);
} else {
return (
<SessionIconButton
iconSize={SessionIconSize.Medium}
iconType={iconType}
notificationCount={notificationCount}
onClick={handleClick}
isSelected={isSelected}
/>
);
}
};
public editProfileHandle() {
window.showEditProfileDialog((avatar: any) => {
this.setState({
avatarPath: avatar,
});
});
}
public render(): JSX.Element {
const {
selectedSection,
@ -141,35 +152,35 @@ export class ActionsPanel extends React.Component<Props, State> {
return (
<div className="module-left-pane__sections-container">
<Section
<this.Section
type={SectionType.Profile}
avatarPath={this.state.avatarPath}
isSelected={isProfilePageSelected}
onSelect={this.handleSectionSelect}
/>
<Section
<this.Section
type={SectionType.Message}
isSelected={isMessagePageSelected}
onSelect={this.handleSectionSelect}
notificationCount={unreadMessageCount}
/>
<Section
<this.Section
type={SectionType.Contact}
isSelected={isContactPageSelected}
onSelect={this.handleSectionSelect}
notificationCount={receivedFriendRequestCount}
/>
<Section
<this.Section
type={SectionType.Channel}
isSelected={isChannelPageSelected}
onSelect={this.handleSectionSelect}
/>
<Section
<this.Section
type={SectionType.Settings}
isSelected={isSettingsPageSelected}
onSelect={this.handleSectionSelect}
/>
<Section
<this.Section
type={SectionType.Moon}
isSelected={isMoonPageSelected}
onSelect={this.handleSectionSelect}

View file

@ -165,7 +165,7 @@ export class LeftPaneChannelSection extends React.Component<Props, State> {
}
public renderHeader(): JSX.Element {
const labels = [window.i18n('channels')];
const labels = [window.i18n('groups')];
return LeftPane.RENDER_HEADER(labels, null);
}

View file

@ -218,33 +218,4 @@ export class LeftPaneSettingSection extends React.Component<any, State> {
settingCategory: category,
});
}
// public updateSearch(searchTerm: string) {
// const { updateSearchTerm, clearSearch } = this.props;
// if (!searchTerm) {
// clearSearch();
// return;
// }
// // reset our pubKeyPasted, we can either have a pasted sessionID or a sessionID got from a search
// this.setState({ pubKeyPasted: '' }, () => {
// window.Session.emptyContentEditableDivs();
// });
// if (updateSearchTerm) {
// updateSearchTerm(searchTerm);
// }
// if (searchTerm.length < 2) {
// return;
// }
// const cleanedTerm = cleanSearchTerm(searchTerm);
// if (!cleanedTerm) {
// return;
// }
// this.debouncedSearch(cleanedTerm);
// }
}

View file

@ -299,7 +299,7 @@ export class RegistrationTabs extends React.Component<{}, State> {
}}
buttonType={SessionButtonType.Brand}
buttonColor={SessionButtonColor.Green}
text={window.i18n('completeSignUp')}
text={window.i18n('getStarted')}
disabled={!enableCompleteSignUp}
/>
</div>
@ -337,7 +337,7 @@ export class RegistrationTabs extends React.Component<{}, State> {
if (signUpMode !== SignUpMode.Default) {
buttonType = SessionButtonType.Brand;
buttonColor = SessionButtonColor.Green;
buttonText = window.i18n('getStarted');
buttonText = window.i18n('continue');
} else {
buttonType = SessionButtonType.BrandOutline;
buttonColor = SessionButtonColor.Green;
@ -449,6 +449,7 @@ export class RegistrationTabs extends React.Component<{}, State> {
type="text"
placeholder={window.i18n('enterDisplayName')}
value={this.state.displayName}
maxLength={window.CONSTANTS.MAX_USERNAME_LENGTH}
onValueChanged={(val: string) => {
this.onDisplayNameChanged(val);
}}
@ -462,6 +463,7 @@ export class RegistrationTabs extends React.Component<{}, State> {
error={this.state.passwordErrorString}
type="password"
placeholder={window.i18n('enterOptionalPassword')}
maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH}
onValueChanged={(val: string) => {
this.onPasswordChanged(val);
}}
@ -475,6 +477,7 @@ export class RegistrationTabs extends React.Component<{}, State> {
error={passwordsDoNotMatch}
type="password"
placeholder={window.i18n('optionalPassword')}
maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH}
onValueChanged={(val: string) => {
this.onPasswordVerifyChanged(val);
}}

View file

@ -9,6 +9,7 @@ interface Props {
type: string;
value?: string;
placeholder: string;
maxLength?: number;
enableShowHide?: boolean;
onValueChanged?: any;
onEnterPressed?: any;
@ -33,7 +34,14 @@ export class SessionInput extends React.PureComponent<Props, State> {
}
public render() {
const { placeholder, type, value, enableShowHide, error } = this.props;
const {
placeholder,
type,
value,
maxLength,
enableShowHide,
error,
} = this.props;
const { forceShow } = this.state;
const correctType = forceShow ? 'text' : type;
@ -46,6 +54,7 @@ export class SessionInput extends React.PureComponent<Props, State> {
type={correctType}
placeholder={placeholder}
value={value}
maxLength={maxLength}
onChange={e => {
this.updateInputValue(e);
}}

View file

@ -58,12 +58,14 @@ export class SessionPasswordModal extends React.Component<Props, State> {
type="password"
id="password-modal-input"
placeholder={placeholders[0]}
maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH}
/>
{action !== PasswordAction.Remove && (
<input
type="password"
id="password-modal-input-confirm"
placeholder={placeholders[1]}
maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH}
/>
)}
</div>
@ -118,6 +120,10 @@ export class SessionPasswordModal extends React.Component<Props, State> {
$('#password-modal-input-confirm').val()
);
if (enteredPassword.length === 0 || enteredPasswordConfirm.length === 0) {
return;
}
// Check passwords enntered
if (
enteredPassword.length === 0 ||

View file

@ -0,0 +1,196 @@
import React from 'react';
import classNames from 'classnames';
import { SessionIcon, SessionIconType } from './icon';
import {
SessionButton,
SessionButtonColor,
SessionButtonType,
} from './SessionButton';
interface State {
error: string;
errorCount: number;
clearDataView: boolean;
}
export class SessionPasswordPrompt extends React.PureComponent<{}, State> {
constructor(props: any) {
super(props);
this.state = {
error: '',
errorCount: 0,
clearDataView: false,
};
this.onKeyUp = this.onKeyUp.bind(this);
this.initLogin = this.initLogin.bind(this);
this.initClearDataView = this.initClearDataView.bind(this);
window.addEventListener('keyup', this.onKeyUp);
}
public render() {
const showResetElements =
this.state.errorCount >= window.CONSTANTS.MAX_LOGIN_TRIES;
const wrapperClass = this.state.clearDataView
? 'clear-data-wrapper'
: 'password-prompt-wrapper';
const containerClass = this.state.clearDataView
? 'clear-data-container'
: 'password-prompt-container';
const infoAreaClass = this.state.clearDataView
? 'warning-info-area'
: 'password-info-area';
const infoTitle = this.state.clearDataView
? window.i18n('clearDataHeader')
: window.i18n('passwordViewTitle');
const buttonGroup = this.state.clearDataView
? this.renderClearDataViewButtons()
: this.renderPasswordViewButtons();
const featureElement = this.state.clearDataView ? (
<p className="text-center">{window.i18n('clearDataExplanation')}</p>
) : (
<input
id="password-prompt-input"
type="password"
autoFocus={true}
defaultValue=""
placeholder={' '}
maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH}
/>
);
const infoIcon = this.state.clearDataView ? (
<SessionIcon
iconType={SessionIconType.Warning}
iconSize={35}
iconColor="#ce0000"
/>
) : (
<SessionIcon
iconType={SessionIconType.Lock}
iconSize={35}
iconColor="#00f782"
/>
);
const errorSection = !this.state.clearDataView && (
<div className="password-prompt-error-section">
{this.state.error && (
<>
{showResetElements ? (
<div className="session-label warning">
{window.i18n('maxPasswordAttempts')}
</div>
) : (
<div className="session-label primary">{this.state.error}</div>
)}
</>
)}
</div>
);
return (
<div className={wrapperClass}>
<div className={containerClass}>
<div className={infoAreaClass}>
{infoIcon}
<h1>{infoTitle}</h1>
</div>
{featureElement}
{errorSection}
{buttonGroup}
</div>
</div>
);
}
public async onKeyUp(event: any) {
switch (event.key) {
case 'Enter':
await this.initLogin();
break;
default:
}
event.preventDefault();
}
public async onLogin(passPhrase: string) {
const trimmed = passPhrase ? passPhrase.trim() : passPhrase;
try {
await window.onLogin(trimmed);
} catch (e) {
// Increment the error counter and show the button if necessary
this.setState({
errorCount: this.state.errorCount + 1,
});
this.setState({ error: e });
}
}
private async initLogin() {
const passPhrase = String($('#password-prompt-input').val());
await this.onLogin(passPhrase);
}
private initClearDataView() {
this.setState({
error: '',
errorCount: 0,
clearDataView: true,
});
}
private renderPasswordViewButtons(): JSX.Element {
const showResetElements =
this.state.errorCount >= window.CONSTANTS.MAX_LOGIN_TRIES;
return (
<div className={classNames(showResetElements && 'button-group')}>
{showResetElements && (
<>
<SessionButton
text="Reset Database"
buttonType={SessionButtonType.BrandOutline}
buttonColor={SessionButtonColor.Danger}
onClick={this.initClearDataView}
/>
</>
)}
<SessionButton
text={window.i18n('unlock')}
buttonType={SessionButtonType.BrandOutline}
buttonColor={SessionButtonColor.Green}
onClick={this.initLogin}
/>
</div>
);
}
private renderClearDataViewButtons(): JSX.Element {
return (
<div className="button-group">
<SessionButton
text={window.i18n('cancel')}
buttonType={SessionButtonType.Default}
buttonColor={SessionButtonColor.Primary}
onClick={() => {
this.setState({ clearDataView: false });
}}
/>
<SessionButton
text={window.i18n('deleteAllDataButton')}
buttonType={SessionButtonType.Default}
buttonColor={SessionButtonColor.Danger}
onClick={window.clearLocalData}
/>
</div>
);
}
}

View file

@ -0,0 +1,19 @@
import React from 'react';
import { SessionIconButton, SessionIconSize, SessionIconType } from './icon';
export class SessionScrollButton extends React.PureComponent {
constructor(props: any) {
super(props);
}
public render() {
return (
<SessionIconButton
iconType={SessionIconType.Chevron}
iconSize={SessionIconSize.Huge}
iconColor={'#FFFFFF'}
/>
);
}
}

View file

@ -5,7 +5,7 @@ import { icons, SessionIconSize, SessionIconType } from '../icon';
export interface Props {
iconType: SessionIconType;
iconSize: SessionIconSize;
iconSize: SessionIconSize | number;
iconColor: string;
iconPadded: boolean;
iconRotation: number;
@ -33,21 +33,25 @@ export class SessionIcon extends React.PureComponent<Props> {
} = this.props;
let iconDimensions;
switch (iconSize) {
case SessionIconSize.Small:
iconDimensions = '15';
break;
case SessionIconSize.Medium:
iconDimensions = '20';
break;
case SessionIconSize.Large:
iconDimensions = '25';
break;
case SessionIconSize.Huge:
iconDimensions = '30';
break;
default:
iconDimensions = '20';
if (typeof iconSize === 'number') {
iconDimensions = iconSize;
} else {
switch (iconSize) {
case SessionIconSize.Small:
iconDimensions = '15';
break;
case SessionIconSize.Medium:
iconDimensions = '20';
break;
case SessionIconSize.Large:
iconDimensions = '25';
break;
case SessionIconSize.Huge:
iconDimensions = '30';
break;
default:
iconDimensions = '20';
}
}
const iconDef = icons[iconType];

View file

@ -15,6 +15,7 @@ export enum SessionSettingCategory {
Permissions = 'permissions',
Notifications = 'notifications',
Devices = 'devices',
Blocked = 'blocked',
}
export enum SessionSettingType {
@ -155,6 +156,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
id="password-lock-input"
defaultValue=""
placeholder={' '}
maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH}
/>
<div className="spacer-xs" />
@ -220,13 +222,25 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
showLinkDeviceButton={!shouldRenderPasswordLock}
category={category}
/>
{shouldRenderPasswordLock ? (
this.renderPasswordLock()
) : (
<div ref={this.settingsViewRef} className="session-settings-list">
{this.renderSettingInCategory()}
</div>
)}
<div className="session-settings-view">
{shouldRenderPasswordLock ? (
this.renderPasswordLock()
) : (
<div ref={this.settingsViewRef} className="session-settings-list">
{this.renderSettingInCategory()}
</div>
)}
{this.renderSessionInfo()}
</div>
</div>
);
}
public renderSessionInfo(): JSX.Element {
return (
<div className="session-settings__version-info">
<span>v{window.versionInfo.version}</span>
<span>{window.versionInfo.commitHash}</span>
</div>
);
}
@ -384,7 +398,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
id: 'media-permissions',
title: window.i18n('mediaPermissionsTitle'),
description: window.i18n('mediaPermissionsDescription'),
hidden: false,
hidden: true, // Hidden until feature works
type: SessionSettingType.Toggle,
category: SessionSettingCategory.Permissions,
setFn: window.toggleMediaPermissions,

10
ts/global.d.ts vendored
View file

@ -1,6 +1,10 @@
interface Window {
CONSTANTS: any;
versionInfo: any;
Events: any;
deleteAllData: any;
clearLocalData: any;
getAccountManager: any;
mnemonic: any;
clipboard: any;
@ -17,6 +21,10 @@ interface Window {
Whisper: any;
ConversationController: any;
// Following function needs to be written in background.js
// getMemberList: any;
onLogin: any;
setPassword: any;
textsecure: any;
Session: any;
@ -44,6 +52,8 @@ interface Window {
getSettingValue: any;
setSettingValue: any;
lokiFeatureFlags: any;
resetDatabase: any;
}
interface Promise<T> {