Merge pull request #726 from vincentbavitz/brand-redesign

Password [set, change, remove] and ContextMenu addition to Main View.
This commit is contained in:
Vince 2020-01-21 08:52:48 +10:00 committed by GitHub
commit adae5b6bf0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 4118 additions and 2753 deletions

View file

@ -1195,6 +1195,19 @@
"description":
"This is a past tense, informational message. In other words, your secure session has been reset."
},
"betaDisclaimerTitle": {
"message": "Thanks for using Session Messenger!",
"description": "Title for beta disclaimer modal"
},
"betaDisclaimerSubtitle": {
"message": "This software is still in its beta phase.",
"description": "Subtitle for beta disclaimer modal"
},
"betaDisclaimerDescription": {
"message":
"While your messages are secured with end-to-end encryption, third parties like your ISP can see who you're talking to while in the beta version. It is also possible that third parties could correlate your public key to your IP address and real identity if they learn your public key.",
"description": "Description for beta disclaimer modal"
},
"quoteThumbnailAlt": {
"message": "Thumbnail of image from quoted message",
"description":
@ -2139,6 +2152,36 @@
"description":
"Button action that the user can click to view their unique seed"
},
"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",
"description": "Description for set account password setting view"
},
"changeAccountPasswordTitle": {
"message": "Change Account Password",
"description": "Prompt for user to change account password in settings view"
},
"changeAccountPasswordDescription": {
"message": "Change your password",
"description": "Description for change account password setting view"
},
"removeAccountPasswordTitle": {
"message": "Remove Account Password",
"description": "Prompt for user to remove account password in settings view"
},
"removeAccountPasswordDescription": {
"message": "Remove the password associated with your account",
"description": "Description for remove account password setting view"
},
"enterPassword": {
"message": "Please enter your password"
},
"confirmPassword": {
"message": "Confirm password"
},
"showSeedPasswordRequest": {
"message": "Please enter your password",
"description": "Request for user to enter password to show seed."
@ -2248,6 +2291,33 @@
"passwordsDoNotMatch": {
"message": "Passwords do not match"
},
"setPasswordInvalid": {
"message": "Passwords do not match"
},
"changePasswordInvalid": {
"message": "The old password you entered is incorrect"
},
"removePasswordInvalid": {
"message": "Incorrect password"
},
"setPasswordTitle": {
"message": "Set Password"
},
"changePasswordTitle": {
"message": "Changed Password"
},
"removePasswordTitle": {
"message": "Removed Password"
},
"setPasswordToastDescription": {
"message": "Your password has been set. Please keep it safe."
},
"changePasswordToastDescription": {
"message": "Your password has been changed. Please keep it safe."
},
"removePasswordToastDescription": {
"message": "You have removed your password."
},
"publicChatExists": {
"message": "You are already connected to this public channel"
},
@ -2364,7 +2434,7 @@
"message": "Adding friends to"
},
"noFriendsToAdd": {
"message": "no friends to add"
"message": "No friends to add"
},
"couldNotDecryptMessage": {
"message": "Couldn't decrypt a message"

View file

@ -821,6 +821,7 @@
};
window.showSeedDialog = window.owsDesktopApp.appView.showSeedDialog;
window.showPasswordDialog = window.owsDesktopApp.appView.showPasswordDialog;
window.generateID = () =>
Math.random()
@ -838,6 +839,7 @@
id: options.id || window.generateID(),
description: options.description || '',
type: options.type || '',
icon: options.icon || '',
shouldFade: options.shouldFade,
};
@ -905,21 +907,24 @@
};
window.toggleMenuBar = () => {
const newValue = !window.getSettingValue('hide-menu-bar');
const newValue = ! window.getSettingValue('hide-menu-bar');
window.Events.setHideMenuBar(newValue);
};
window.toggleSpellCheck = () => {
const newValue = !window.getSettingValue('spell-check');
const newValue = ! window.getSettingValue('spell-check');
window.Events.setSpellCheck(newValue);
};
window.toggleLinkPreview = () => {
const newValue = !window.getSettingValue('link-preview-setting');
const newValue = ! window.getSettingValue('link-preview-setting');
window.Events.setLinkPreviewSetting(newValue);
};
window.toggleMediaPermissions = () => {};
window.toggleMediaPermissions = () => {
const mediaPermissions = window.getMediaPermissions();
window.setMediaPermissions(!mediaPermissions);
};
window.sendGroupInvitations = (serverInfo, pubkeys) => {
pubkeys.forEach(async pubkey => {
@ -1180,12 +1185,6 @@
}
});
Whisper.events.on('showPasswordDialog', options => {
if (appView) {
appView.showPasswordDialog(options);
}
});
Whisper.events.on('showSeedDialog', async () => {
if (appView) {
appView.showSeedDialog();

View file

@ -70,6 +70,11 @@ const {
const {
SessionSeedModal,
} = require('../../ts/components/session/SessionSeedModal');
const {
SessionPasswordModal,
} = require('../../ts/components/session/SessionPasswordModal');
const {
SessionConfirm,
} = require('../../ts/components/session/SessionConfirm');
@ -284,6 +289,7 @@ exports.setup = (options = {}) => {
SessionModal,
SessionQRModal,
SessionSeedModal,
SessionPasswordModal,
SessionDropdown,
MediaGallery,
Message,

View file

@ -17,6 +17,7 @@
this.applyHideMenu();
this.showSeedDialog = this.showSeedDialog.bind(this);
this.showPasswordDialog = this.showPasswordDialog.bind(this);
},
events: {
'click .openInstaller': 'openInstaller', // NetworkStatusView has this button
@ -198,8 +199,8 @@
this.el.append(dialog.el);
dialog.focusInput();
},
showPasswordDialog() {
const dialog = Whisper.PasswordDialogView();
showPasswordDialog(options) {
const dialog = new Whisper.PasswordDialogView(options);
this.el.append(dialog.el);
},
showSeedDialog() {

View file

@ -1,4 +1,4 @@
/* global Whisper, i18n, window */
/* global Whisper, window */
// eslint-disable-next-line func-names
(function() {
@ -8,37 +8,31 @@
Whisper.BetaReleaseDisclaimer = Whisper.View.extend({
className: 'loki-dialog beta-disclaimer-dialog modal',
templateName: 'beta-disclaimer-dialog',
initialize(options = {}) {
this.okText = options.okText || i18n('ok');
initialize() {
this.close = this.close.bind(this);
this.render();
this.$('.betaDisclaimerView').show();
this.$('.beta-disclaimer-dialog').bind('keyup', event =>
this.onKeyup(event)
);
},
events: {
'click .ok': 'close',
},
render_attributes() {
return {
ok: this.okText,
};
render() {
this.dialogView = new Whisper.ReactWrapperView({
className: 'session-beta-disclaimer',
Component: window.Signal.Components.SessionConfirm,
props: {
title: window.i18n('betaDisclaimerTitle'),
message: window.i18n('betaDisclaimerSubtitle'),
messageSub: window.i18n('betaDisclaimerDescription'),
hideCancel: true,
onClickOk: this.close,
},
});
this.$el.append(this.dialogView.el);
return this;
},
close() {
window.storage.put('betaReleaseDisclaimerAccepted', true);
this.remove();
},
onKeyup(event) {
switch (event.key) {
case 'Enter':
case 'Escape':
case 'Esc':
this.close();
break;
default:
break;
}
},
});
})();

View file

@ -1,4 +1,4 @@
/* global Whisper, i18n, _, Signal, passwordUtil */
/* global Whisper */
// eslint-disable-next-line func-names
(function() {
@ -6,223 +6,39 @@
window.Whisper = window.Whisper || {};
const PasswordDialogView = Whisper.View.extend({
Whisper.PasswordDialogView = Whisper.View.extend({
className: 'loki-dialog password-dialog modal',
templateName: 'password-dialog',
initialize(options) {
this.type = options.type;
this.resolve = options.resolve;
this.okText = options.okText || i18n('ok');
this.reject = options.reject;
this.cancelText = options.cancelText || i18n('cancel');
this.title = options.title;
this.close = this.close.bind(this);
this.onOk = this.onOk.bind(this);
this.props = options;
this.render();
this.updateUI();
},
events: {
keyup: 'onKeyup',
'click .ok': 'ok',
'click .cancel': 'cancel',
},
render_attributes() {
return {
showCancel: !this.hideCancel,
cancel: this.cancelText,
ok: this.okText,
title: this.title,
};
},
async updateUI() {
if (this.disableOkButton()) {
this.$('.ok').prop('disabled', true);
} else {
this.$('.ok').prop('disabled', false);
}
},
disableOkButton() {
const password = this.$('#password').val();
return _.isEmpty(password);
},
async validate() {
const password = this.$('#password').val();
const passwordConfirmation = this.$('#password-confirmation').val();
const pairValidation = this.validatePasswordPair(
password,
passwordConfirmation
);
const hashValidation = await this.validatePasswordHash(password);
return pairValidation || hashValidation;
},
async validatePasswordHash(password) {
// Check if the password matches the hash we have stored
const hash = await Signal.Data.getPasswordHash();
if (hash && !passwordUtil.matchesHash(password, hash)) {
return i18n('invalidPassword');
}
return null;
},
validatePasswordPair(password, passwordConfirmation) {
if (!_.isEmpty(password)) {
// Check if the password is first valid
const passwordValidation = passwordUtil.validatePassword(
password,
i18n
);
if (passwordValidation) {
return passwordValidation;
}
// Check if the confirmation password is the same
if (
!passwordConfirmation ||
password.trim() !== passwordConfirmation.trim()
) {
return i18n('passwordsDoNotMatch');
}
}
return null;
},
okPressed() {
const password = this.$('#password').val();
if (this.type === 'set') {
window.setPassword(password.trim());
} else if (this.type === 'remove') {
window.setPassword(null, password.trim());
}
},
okErrored() {
if (this.type === 'set') {
this.showError(i18n('setPasswordFail'));
} else if (this.type === 'remove') {
this.showError(i18n('removePasswordFail'));
}
},
async ok() {
const error = await this.validate();
if (error) {
this.showError(error);
return;
}
// Clear any errors
this.showError(null);
try {
this.okPressed();
this.remove();
if (this.resolve) {
this.resolve();
}
} catch (e) {
this.okErrored();
}
},
cancel() {
this.remove();
if (this.reject) {
this.reject();
}
},
onKeyup(event) {
this.updateUI();
switch (event.key) {
case 'Enter':
this.ok();
break;
case 'Escape':
case 'Esc':
this.cancel();
break;
default:
return;
}
event.preventDefault();
},
focusCancel() {
this.$('.cancel').focus();
},
showError(message) {
if (_.isEmpty(message)) {
this.$('.error').text('');
this.$('.error').hide();
} else {
this.$('.error').text(`Error: ${message}`);
this.$('.error').show();
}
},
});
const ChangePasswordDialogView = PasswordDialogView.extend({
templateName: 'password-change-dialog',
disableOkButton() {
const oldPassword = this.$('#old-password').val();
const newPassword = this.$('#new-password').val();
return _.isEmpty(oldPassword) || _.isEmpty(newPassword);
},
async validate() {
const oldPassword = this.$('#old-password').val();
// Validate the old password
if (!_.isEmpty(oldPassword)) {
const oldPasswordValidation = passwordUtil.validatePassword(
oldPassword,
i18n
);
if (oldPasswordValidation) {
return oldPasswordValidation;
}
} else {
return i18n('typeInOldPassword');
}
const password = this.$('#new-password').val();
const passwordConfirmation = this.$('#new-password-confirmation').val();
const pairValidation = this.validatePasswordPair(
password,
passwordConfirmation
);
const hashValidation = await this.validatePasswordHash(oldPassword);
return pairValidation || hashValidation;
},
okPressed() {
const oldPassword = this.$('#old-password').val();
const newPassword = this.$('#new-password').val();
window.setPassword(newPassword.trim(), oldPassword.trim());
},
okErrored() {
this.showError(i18n('changePasswordFail'));
},
});
Whisper.getPasswordDialogView = (type, resolve, reject) => {
// This is a differently styled dialog
if (type === 'change') {
return new ChangePasswordDialogView({
title: i18n('changePassword'),
okTitle: i18n('change'),
resolve,
reject,
render() {
this.dialogView = new Whisper.ReactWrapperView({
className: 'password-dialog-wrapper',
Component: window.Signal.Components.SessionPasswordModal,
props: {
onClose: this.close,
onOk: this.onOk,
...this.props,
},
});
}
// Set and Remove is basically the same UI
const title =
type === 'remove' ? i18n('removePassword') : i18n('setPassword');
const okTitle = type === 'remove' ? i18n('remove') : i18n('set');
return new PasswordDialogView({
title,
okTitle,
type,
resolve,
reject,
});
};
this.$el.append(this.dialogView.el);
return this;
},
onOk(action) {
if (this.props.onSuccess) {
this.props.onSuccess(action);
}
},
close() {
this.remove();
},
});
})();

View file

@ -12,6 +12,7 @@
title: options.title,
id: options.id,
description: options.description,
icon: options.icon,
fadeToast: this.fadeToast.bind(this),
closeToast: this.closeToast.bind(this),
};
@ -32,6 +33,7 @@
this.props.id = options.id;
this.props.description = options.description || '';
this.props.type = options.type || '';
this.props.icon = options.icon || '';
this.props.shouldFade = options.shouldFade !== false;
this.toastView.update(this.props);

View file

@ -37,7 +37,7 @@
"test-electron": "yarn grunt test",
"test-node": "mocha --recursive --exit test/app test/modules ts/test libloki/test/node",
"test-node-coverage": "nyc --reporter=lcov --reporter=text mocha --recursive test/app test/modules ts/test libloki/test/node",
"test-node-coverage-html": "nyc --reporter=lcov --reporter=html mocha --recursive test/app test/modules ts/test libloki/test/node",
"test-node-coverage-html": "nyc --reporter=lcov --reporter=html mocha --recursive test/a/* */pp test/modules ts/test libloki/test/node",
"eslint": "eslint .",
"lint": "yarn format --list-different && yarn lint-windows",
"dev-lint": "yarn format --list-different; yarn lint-windows",
@ -56,6 +56,7 @@
"@journeyapps/sqlcipher": "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#2e28733b61640556b0272a3bfc78b0357daf71e6",
"@sindresorhus/is": "0.8.0",
"@types/dompurify": "^2.0.0",
"@types/rc-slider": "^8.6.5",
"backbone": "1.3.3",
"blob-util": "1.3.0",
"blueimp-canvas-to-blob": "3.14.0",
@ -101,6 +102,7 @@
"pify": "3.0.0",
"protobufjs": "6.8.6",
"proxy-agent": "3.0.3",
"rc-slider": "^8.7.1",
"react": "16.8.3",
"react-contextmenu": "2.11.0",
"react-dom": "16.8.3",

View file

@ -95,6 +95,8 @@ window.setPassword = (passPhrase, oldPhrase) =>
ipc.send('set-password', passPhrase, oldPhrase);
});
window.passwordUtil = require('./app/password_util');
// We never do these in our code, so we'll prevent it everywhere
window.open = () => null;
// eslint-disable-next-line no-eval, no-multi-assign
@ -189,6 +191,17 @@ window.getSettingValue = (settingID, comparisonValue = null) => {
// Comparison value allows you to pull boolean values from any type.
// Eg. window.getSettingValue('theme', 'light')
// returns 'false' when the value is 'dark'.
if (settingID === 'media-permissions') {
let permissionValue;
// eslint-disable-next-line more/no-then
window.getMediaPermissions().then(value => {
permissionValue = value;
});
return permissionValue;
}
const settingVal = window.storage.get(settingID);
return comparisonValue ? !!settingVal === comparisonValue : settingVal;
};
@ -226,6 +239,9 @@ installSetter('link-preview-setting', 'setLinkPreviewSetting');
installGetter('spell-check', 'getSpellCheck');
installSetter('spell-check', 'setSpellCheck');
installGetter('media-permissions', 'getMediaPermissions');
installGetter('media-permissions', 'setMediaPermissions');
window.getMediaPermissions = () =>
new Promise((resolve, reject) => {
ipc.once('get-success-media-permissions', (_event, error, value) => {
@ -386,7 +402,6 @@ window.libphonenumber.PhoneNumberFormat = require('google-libphonenumber').Phone
window.loadImage = require('blueimp-load-image');
window.getGuid = require('uuid/v4');
window.profileImages = require('./app/profile_images');
window.passwordUtil = require('./app/password_util');
window.React = require('react');
window.ReactDOM = require('react-dom');

View file

@ -134,11 +134,6 @@
padding: 4px;
user-select: none;
&:hover:not(.member-selected) {
background-color: $color-light-20;
}
background-color: $color-light-10;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
@ -153,10 +148,6 @@
color: darkslategrey;
}
}
.member-selected {
background-color: $color-light-35;
}
}
.mention-profile-name {
@ -179,30 +170,6 @@
background-color: rgba(255, 197, 50, 0.2);
}
.dark-theme {
.member-list-container,
.create-group-dialog,
.invite-friends-dialog {
.member-item {
&:hover:not(.member-selected) {
background-color: $color-dark-55;
}
background-color: $color-dark-70;
color: white;
.pubkey-part {
color: rgb(230, 230, 230);
}
}
.member-selected {
background-color: $color-dark-60;
}
}
}
.module-conversation-list-item--mentioned-us {
border-left: 4px solid #ffb000 !important;
}

View file

@ -1421,8 +1421,6 @@
// Module: Conversation Header
.module-conversation-header {
padding-left: 16px;
padding-right: 16px;
display: flex;
flex-direction: row;
align-items: center;

View file

@ -0,0 +1,268 @@
.slider {
&-wrapper {
display: flex;
align-items: center;
margin: 20px 10px;
}
&-info {
display: block;
margin-left: 20px;
text-align: center;
p {
white-space: nowrap;
}
}
}
.rc-slider {
position: relative;
height: 14px;
padding: 5px 0;
width: 100%;
border-radius: 6px;
-ms-touch-action: none;
touch-action: none;
box-sizing: border-box;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.rc-slider * {
box-sizing: border-box;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.rc-slider-rail {
position: absolute;
width: 100%;
background-color: $session-shade-6;
height: 8px;
border-radius: 6px;
margin: 0px 0px 0px -1px;
}
.rc-slider-handle {
transition: $session-transition-duration;
position: absolute;
width: 6px;
height: 25px;
cursor: pointer;
cursor: -webkit-grab;
margin-top: -9.5px;
margin-left: -2.3px;
cursor: grab;
border-radius: 4px;
/* border: solid 2px rgba(0, 247, 130, 0.8); */
background-color: rgba(0, 247, 130, 1);
-ms-touch-action: pan-x;
touch-action: pan-x;
}
.rc-slider-handle:focus {
border-color: $session-color-green;
box-shadow: 0 0 0 5px rgba($session-color-white, 0.2);
outline: none;
}
.rc-slider-handle-click-focused:focus {
border-color: rgba($session-color-white, 0.2);
box-shadow: unset;
}
.rc-slider-handle:hover {
border-color: $session-color-green;
}
.rc-slider-handle:active {
border-color: $session-color-green;
box-shadow: 0 0 5px rgba($session-color-white, 0.2);
cursor: -webkit-grabbing;
cursor: grabbing;
}
.rc-slider-mark {
position: absolute;
top: 18px;
left: 0;
width: 100%;
font-size: 12px;
}
.rc-slider-mark-text {
position: absolute;
display: inline-block;
vertical-align: middle;
text-align: center;
cursor: pointer;
color: #999;
}
.rc-slider-mark-text-active {
color: #666;
}
.rc-slider-step {
position: absolute;
width: 100%;
height: 4px;
top: -4px;
background: transparent;
}
.rc-slider-dot {
position: absolute;
bottom: -2px;
margin-left: -4px;
width: 3px;
height: 6px;
background-color: $session-shade-6;
cursor: pointer;
border-radius: 2px;
vertical-align: middle;
}
.rc-slider-dot:first-child {
margin-left: -2px;
}
.rc-slider-dot:last-child {
margin-right: -2px;
}
.rc-slider-dot-active {
border-color: #96dbfa;
}
.rc-slider-dot-reverse {
margin-left: 0;
margin-right: -4px;
}
.rc-slider-disabled {
background-color: #e9e9e9;
}
.rc-slider-disabled .rc-slider-handle,
.rc-slider-disabled .rc-slider-dot {
border-color: #ccc;
box-shadow: none;
background-color: #fff;
cursor: not-allowed;
}
.rc-slider-disabled .rc-slider-mark-text,
.rc-slider-disabled .rc-slider-dot {
cursor: not-allowed !important;
}
.rc-slider-vertical {
width: 14px;
height: 100%;
padding: 0 5px;
}
.rc-slider-vertical .rc-slider-rail {
height: 100%;
width: 4px;
}
.rc-slider-vertical .rc-slider-handle {
margin-left: -5px;
-ms-touch-action: pan-y;
touch-action: pan-y;
}
.rc-slider-vertical .rc-slider-mark {
top: 0;
left: 18px;
height: 100%;
}
.rc-slider-vertical .rc-slider-step {
height: 100%;
width: 4px;
}
.rc-slider-vertical .rc-slider-dot {
left: 2px;
margin-bottom: -4px;
}
.rc-slider-vertical .rc-slider-dot:first-child {
margin-bottom: -4px;
}
.rc-slider-vertical .rc-slider-dot:last-child {
margin-bottom: -4px;
}
.rc-slider-tooltip-zoom-down-enter,
.rc-slider-tooltip-zoom-down-appear {
animation-duration: 0.3s;
animation-fill-mode: both;
display: block !important;
animation-play-state: paused;
}
.rc-slider-tooltip-zoom-down-leave {
animation-duration: 0.3s;
animation-fill-mode: both;
display: block !important;
animation-play-state: paused;
}
.rc-slider-tooltip-zoom-down-enter.rc-slider-tooltip-zoom-down-enter-active,
.rc-slider-tooltip-zoom-down-appear.rc-slider-tooltip-zoom-down-appear-active {
animation-name: rcSliderTooltipZoomDownIn;
animation-play-state: running;
}
.rc-slider-tooltip-zoom-down-leave.rc-slider-tooltip-zoom-down-leave-active {
animation-name: rcSliderTooltipZoomDownOut;
animation-play-state: running;
}
.rc-slider-tooltip-zoom-down-enter,
.rc-slider-tooltip-zoom-down-appear {
transform: scale(0, 0);
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
.rc-slider-tooltip-zoom-down-leave {
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
}
@keyframes rcSliderTooltipZoomDownIn {
0% {
opacity: 0;
transform-origin: 50% 100%;
transform: scale(0, 0);
}
100% {
transform-origin: 50% 100%;
transform: scale(1, 1);
}
}
@keyframes rcSliderTooltipZoomDownOut {
0% {
transform-origin: 50% 100%;
transform: scale(1, 1);
}
100% {
opacity: 0;
transform-origin: 50% 100%;
transform: scale(0, 0);
}
}
.rc-slider-tooltip {
position: absolute;
left: -9999px;
top: -9999px;
visibility: visible;
box-sizing: border-box;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.rc-slider-tooltip * {
box-sizing: border-box;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.rc-slider-tooltip-hidden {
display: none;
}
.rc-slider-tooltip-placement-top {
padding: 4px 0 8px 0;
}
.rc-slider-tooltip-inner {
padding: 6px 2px;
min-width: 24px;
height: 24px;
font-size: 12px;
line-height: 1;
color: #fff;
text-align: center;
text-decoration: none;
background-color: #6c6c6c;
border-radius: 6px;
box-shadow: 0 0 4px #d9d9d9;
}
.rc-slider-tooltip-arrow {
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
}
.rc-slider-tooltip-placement-top .rc-slider-tooltip-arrow {
bottom: 4px;
left: 50%;
margin-left: -4px;
border-width: 4px 4px 0;
border-top-color: #6c6c6c;
}

View file

@ -75,6 +75,7 @@ $session-color-info: $session-shade-11;
$session-color-success: #35d388;
$session-color-error: #edd422;
$session-color-warning: $session-shade-17;
$session-color-warning-alt: #ff9d00;
$session-color-light-grey: #a0a0a0;
@ -227,6 +228,7 @@ $session_message-container-border-radius: 5px;
justify-content: center;
font-weight: 700;
user-select: none;
white-space: nowrap;
cursor: pointer;
transition: $session-transition-duration;
background-color: rgba(0, 0, 0, 0);
@ -280,9 +282,9 @@ $session_message-container-border-radius: 5px;
}
}
&.warning {
background-color: $session-color-warning;
background-color: $session-color-warning-alt;
&.disabled {
background-color: rgba($session-color-warning, 0.6);
background-color: rgba($session-color-warning-alt, 0.6);
}
}
}
@ -338,7 +340,7 @@ $session_message-container-border-radius: 5px;
border-radius: 0px;
}
&:hover {
& > *:not(svg):hover {
filter: brightness(80%);
}
}
@ -363,7 +365,7 @@ $session_message-container-border-radius: 5px;
background-color: $session-color-danger;
}
&.warning {
background-color: $session-color-warning;
background-color: $session-color-warning-alt;
}
}
@ -430,7 +432,7 @@ $session-element-border-green: 4px solid $session-color-green;
@mixin standard-icon-button() {
color: $session-color-white;
opacity: 0.9;
opacity: 0.6;
&:hover {
opacity: 1;
@ -439,6 +441,7 @@ $session-element-border-green: 4px solid $session-color-green;
.module-conversation-header__title-flex,
.module-conversation-header__title {
font-family: Wasa;
width: 100%;
display: flex;
@ -489,9 +492,9 @@ label {
@include standard-icon-button();
}
.module-conversation-header,
.message-selection-overlay {
height: $session-conversation-header-height;
.module-conversation-header {
position: relative;
padding: 0px $session-margin-lg 0px $session-margin-sm;
}
.title-wrapper {
@ -603,7 +606,7 @@ label {
@include set-toast-theme($session-color-success);
}
&.warning {
@include set-toast-theme($session-color-warning);
@include set-toast-theme($session-color-warning-alt);
}
&.error {
@include set-toast-theme($session-color-error);
@ -1117,3 +1120,59 @@ input {
.friend-selection-list {
width: 40vh;
}
.session-beta-disclaimer {
.session-modal {
width: 600px;
&__header {
font-size: 17px;
}
&__body > div:first-child {
padding: $session-margin-lg;
}
.session-confirm-main-message {
opacity: 0.8;
margin-bottom: $session-margin-xs;
}
.session-confirm-sub-message {
text-align: center;
}
}
}
button.module-scroll-down {
&__button {
background-color: $session-shade-6;
}
&__button:hover {
&__icon {
transition: $session-transition-duration;
@include color-svg('../images/down.svg', $session-color-white);
}
}
&__icon {
@include color-svg('../images/down.svg', rgba($session-color-white, 0.6));
}
}
/* Memberlist */
.member-list-container .member {
&-item {
padding: $session-margin-sm;
background-color: $session-shade-5;
&:hover:not(.member-selected) {
background-color: $session-shade-7;
}
}
&-selected {
background-color: $session-shade-8;
}
}

View file

@ -124,6 +124,7 @@ $session-compose-margin: 20px;
.module-left-pane {
border-right: none !important;
width: 300px;
position: relative;
height: -webkit-fill-available;
@at-root .light-theme #{&} {
@ -355,6 +356,9 @@ $session-compose-margin: 20px;
@mixin bottom-buttons() {
display: flex;
flex-direction: row;
position: absolute;
bottom: 0px;
width: 100%;
@at-root .light-theme #{&} {
background-color: $session-color-white;

View file

@ -242,13 +242,11 @@ $session-compose-margin: 20px;
.session-search-input {
height: $session-search-input-height;
width: 100%;
margin-right: 1px;
margin-bottom: 10px;
background: $session-shade-4;
color: $session-color-white;
display: inline-flex;
flex-shrink: 0;
.session-icon-button {
margin: auto 10px;

View file

@ -31,5 +31,7 @@
@import 'session_theme_dark_left_pane';
@import 'session_group_panel';
@import 'session-slider';
// Installer
@import 'options';

View file

@ -91,7 +91,7 @@ export class EditProfileDialog extends React.Component<Props, State> {
<SessionIconButton
iconType={SessionIconType.Upload}
iconSize={SessionIconSize.Large}
iconSize={SessionIconSize.Huge}
onClick={() => {
const el = this.inputEl.current;
if (el) {

View file

@ -8,7 +8,7 @@ import {
export const MainViewController = {
renderMessageView: () => {
if(document.getElementById('main-view')) {
if (document.getElementById('main-view')) {
ReactDOM.render(<MessageView />, document.getElementById('main-view'));
}
},
@ -29,7 +29,11 @@ export class MessageView extends React.Component {
<div className="conversation-header" />
<div className="container">
<div className="content">
<img src="images/session/full-logo.svg" className="session-full-logo" alt="full-brand-logo"/>
<img
src="images/session/full-logo.svg"
className="session-full-logo"
alt="full-brand-logo"
/>
</div>
</div>
</div>

View file

@ -2,7 +2,12 @@ import React from 'react';
import { Avatar } from '../Avatar';
import { Colors, LocalizerType } from '../../types/Util';
import { ContextMenu, MenuItem, SubMenu } from 'react-contextmenu';
import {
ContextMenu,
ContextMenuTrigger,
MenuItem,
SubMenu,
} from 'react-contextmenu';
import {
SessionIconButton,
@ -243,7 +248,7 @@ export class ConversationHeader extends React.Component<Props> {
);
}
public renderOptions() {
public renderOptions(triggerId: string) {
const { showBackButton } = this.props;
if (showBackButton) {
@ -251,12 +256,17 @@ export class ConversationHeader extends React.Component<Props> {
}
return (
<>
<ContextMenuTrigger
id={triggerId}
ref={this.menuTriggerRef}
holdToDisplay={1}
>
<SessionIconButton
iconType={SessionIconType.Ellipses}
iconSize={SessionIconSize.Large}
onClick={this.showMenuBound}
/>
</>
</ContextMenuTrigger>
);
}
@ -347,7 +357,7 @@ export class ConversationHeader extends React.Component<Props> {
{this.renderBackButton()}
<div className="module-conversation-header__title-container">
<div className="module-conversation-header__title-flex">
{this.renderOptions()}
{this.renderOptions(triggerId)}
{this.renderTitle()}
{isPrivateGroup ? this.renderMemberCount() : null}
</div>

View file

@ -6,6 +6,9 @@ import {
SessionIconType,
} from '../session/icon';
import { SessionModal } from '../session/SessionModal';
import { SessionButton } from '../session/SessionButton';
interface Props {
friendList: Array<any>;
chatName: string;
@ -67,15 +70,11 @@ export class InviteFriendsDialog extends React.Component<Props, State> {
const hasFriends = this.state.friendList.length !== 0;
return (
<div className="content">
<div className="session-modal__header__close">
<SessionIconButton
iconType={SessionIconType.Exit}
iconSize={SessionIconSize.Small}
onClick={this.closeDialog}
/>
</div>
<p className="titleText">{titleText}</p>
<SessionModal
title={titleText}
onOk={() => null}
onClose={this.closeDialog}
>
<div className="friend-selection-list">
<MemberList
members={this.state.friendList}
@ -85,22 +84,22 @@ export class InviteFriendsDialog extends React.Component<Props, State> {
/>
</div>
{hasFriends ? null : (
<p className="no-friends">{window.i18n('noFriendsToAdd')}</p>
<>
<div className="spacer-lg" />
<p className="no-friends">{window.i18n('noFriendsToAdd')}</p>
<div className="spacer-lg" />
</>
)}
<div className="buttons">
<button className="cancel" tabIndex={0} onClick={this.closeDialog}>
{cancelText}
</button>
<button
className="ok"
<div className="session-modal__button-group">
<SessionButton text={cancelText} onClick={this.closeDialog} />
<SessionButton
text={okText}
disabled={!hasFriends}
tabIndex={0}
onClick={this.onClickOK}
>
{okText}
</button>
/>
</div>
</div>
</SessionModal>
);
}

View file

@ -64,33 +64,37 @@ export class LeftPaneSettingSection extends React.Component<any, State> {
return (
<>
{categories.map(item => (
<div
key={item.id}
className={classNames(
'left-pane-setting-category-list-item',
item.id === this.state.settingCategory ? 'active' : ''
)}
role="link"
onClick={(): void => {
this.setCategory(item.id);
}}
>
<div>
<strong>{item.title}</strong>
<br />
<span className="text-subtle">{item.description}</span>
</div>
<>
{!item.hidden && (
<div
key={item.id}
className={classNames(
'left-pane-setting-category-list-item',
item.id === this.state.settingCategory ? 'active' : ''
)}
role="link"
onClick={() => {
this.setCategory(item.id);
}}
>
<div>
<strong>{item.title}</strong>
<br />
<span className="text-subtle">{item.description}</span>
</div>
<div>
{item.id === this.state.settingCategory && (
<SessionIcon
iconSize={SessionIconSize.Medium}
iconType={SessionIconType.Chevron}
iconRotation={270}
/>
)}
</div>
</div>
<div>
{item.id === this.state.settingCategory && (
<SessionIcon
iconSize={SessionIconSize.Medium}
iconType={SessionIconType.Chevron}
iconRotation={270}
/>
)}
</div>
</div>
)}
</>
))}
</>
);
@ -122,7 +126,7 @@ export class LeftPaneSettingSection extends React.Component<any, State> {
>
<SessionIcon
iconType={SessionIconType.Caret}
iconSize={SessionIconSize.Large}
iconSize={SessionIconSize.Huge}
/>
</SessionButton>
</div>
@ -173,21 +177,25 @@ export class LeftPaneSettingSection extends React.Component<any, State> {
id: SessionSettingCategory.General,
title: window.i18n('generalSettingsTitle'),
description: window.i18n('generalSettingsDescription'),
hidden: false,
},
{
id: SessionSettingCategory.Privacy,
title: window.i18n('privacySettingsTitle'),
description: window.i18n('privacySettingsDescription'),
hidden: false,
},
{
id: SessionSettingCategory.Permissions,
title: window.i18n('permissionSettingsTitle'),
description: window.i18n('permissionSettingsDescription'),
hidden: true,
},
{
id: SessionSettingCategory.Notifications,
title: window.i18n('notificationSettingsTitle'),
description: window.i18n('notificationSettingsDescription'),
hidden: false,
},
];
}

View file

@ -0,0 +1,181 @@
import React from 'react';
import { SessionModal } from './SessionModal';
import { SessionButton, SessionButtonColor } from './SessionButton';
export enum PasswordAction {
Set = 'set',
Change = 'change',
Remove = 'remove',
}
interface Props {
action: PasswordAction;
onOk: any;
onClose: any;
}
interface State {
error: string | null;
}
export class SessionPasswordModal extends React.Component<Props, State> {
constructor(props: any) {
super(props);
this.state = {
error: null,
};
this.showError = this.showError.bind(this);
this.setPassword = this.setPassword.bind(this);
this.closeDialog = this.closeDialog.bind(this);
}
public render() {
const { action, onOk } = this.props;
const placeholders =
this.props.action === PasswordAction.Change
? [window.i18n('typeInOldPassword'), window.i18n('enterPassword')]
: [window.i18n('enterPassword'), window.i18n('confirmPassword')];
const confirmButtonColor =
this.props.action === PasswordAction.Remove
? SessionButtonColor.Danger
: SessionButtonColor.Primary;
return (
<SessionModal
title={window.i18n(`${action}Password`)}
onOk={() => null}
onClose={this.closeDialog}
>
<div className="spacer-sm" />
<div className="session-modal__input-group">
<input
type="password"
id="password-modal-input"
placeholder={placeholders[0]}
/>
{action !== PasswordAction.Remove && (
<input
type="password"
id="password-modal-input-confirm"
placeholder={placeholders[1]}
/>
)}
</div>
<div className="spacer-sm" />
{this.showError()}
<div className="session-modal__button-group">
<SessionButton
text={window.i18n('ok')}
buttonColor={confirmButtonColor}
onClick={async () => this.setPassword(onOk)}
/>
<SessionButton
text={window.i18n('cancel')}
onClick={this.closeDialog}
/>
</div>
</SessionModal>
);
}
public async validatePasswordHash(password: string | null) {
// Check if the password matches the hash we have stored
const hash = await window.Signal.Data.getPasswordHash();
if (hash && !window.passwordUtil.matchesHash(password, hash)) {
return false;
}
return true;
}
private showError() {
const message = this.state.error;
return (
<>
{message && (
<>
<div className="session-label warning">{message}</div>
<div className="spacer-lg" />
</>
)}
</>
);
}
private async setPassword(onSuccess: any) {
const enteredPassword = String($('#password-modal-input').val());
const enteredPasswordConfirm = String(
$('#password-modal-input-confirm').val()
);
// Check passwords enntered
if (
enteredPassword.length === 0 ||
(this.props.action === PasswordAction.Change &&
enteredPasswordConfirm.length === 0)
) {
this.setState({
error: window.i18n('noGivenPassword'),
});
return;
}
// Passwords match or remove password successful
const newPassword =
this.props.action === PasswordAction.Remove
? null
: enteredPasswordConfirm;
const oldPassword =
this.props.action === PasswordAction.Set ? null : enteredPassword;
// Check if password match, when setting, changing or removing
const valid =
this.props.action !== PasswordAction.Set
? !!await this.validatePasswordHash(oldPassword)
: enteredPassword === enteredPasswordConfirm;
if (!valid) {
this.setState({
error: window.i18n(`${this.props.action}PasswordInvalid`),
});
return;
}
await window.setPassword(newPassword, oldPassword);
const toastParams = {
title: window.i18n(`${this.props.action}PasswordTitle`),
description: window.i18n(`${this.props.action}PasswordToastDescription`),
type: this.props.action !== PasswordAction.Remove ? 'success' : 'warning',
icon: this.props.action !== PasswordAction.Remove ? 'lock' : undefined,
};
window.pushToast({
id: 'set-password-success-toast',
...toastParams,
});
onSuccess(this.props.action);
this.closeDialog();
}
private closeDialog() {
window.removeEventListener('keyup', this.onEnter);
if (this.props.onClose) {
this.props.onClose();
}
}
}

View file

@ -19,6 +19,7 @@ interface Props {
title: string;
id?: string;
type?: SessionToastType;
icon?: SessionIconType;
description?: string;
closeToast: any;
}
@ -29,30 +30,33 @@ export class SessionToast extends React.PureComponent<Props> {
}
public render() {
const { title, description, type } = this.props;
const { title, description, type, icon } = this.props;
const toastType = type ? type : SessionToastType.Info;
const toastDesc = description ? description : '';
const toastIconSize = toastDesc
? SessionIconSize.Large
? SessionIconSize.Huge
: SessionIconSize.Medium;
let toastIcon;
switch (type) {
case SessionToastType.Info:
toastIcon = SessionIconType.Info;
break;
case SessionToastType.Success:
toastIcon = SessionIconType.Check;
break;
case SessionToastType.Error:
toastIcon = SessionIconType.Error;
break;
case SessionToastType.Warning:
toastIcon = SessionIconType.Warning;
break;
default:
toastIcon = SessionIconType.Info;
// Set a custom icon or allow the theme to define the icon
let toastIcon = icon || undefined;
if (!toastIcon) {
switch (type) {
case SessionToastType.Info:
toastIcon = SessionIconType.Info;
break;
case SessionToastType.Success:
toastIcon = SessionIconType.Check;
break;
case SessionToastType.Error:
toastIcon = SessionIconType.Error;
break;
case SessionToastType.Warning:
toastIcon = SessionIconType.Warning;
break;
default:
toastIcon = SessionIconType.Info;
}
}
return (

View file

@ -19,6 +19,7 @@ export enum SessionIconType {
Gear = 'gear',
Globe = 'globe',
Info = 'info',
Lock = 'lock',
Microphone = 'microphone',
Moon = 'moon',
Reply = 'reply',
@ -35,6 +36,7 @@ export enum SessionIconSize {
Small = 'small',
Medium = 'medium',
Large = 'large',
Huge = 'huge',
}
export const icons = {
@ -95,7 +97,7 @@ export const icons = {
[SessionIconType.Ellipses]: {
path:
'M30,16c4.411,0,8-3.589,8-8s-3.589-8-8-8s-8,3.589-8,8S25.589,16,30,16z M30,22c-4.411,0-8,3.589-8,8s3.589,8,8,8s8-3.589,8-8S34.411,22,30,22z M30,44c-4.411,0-8,3.589-8,8s3.589,8,8,8s8-3.589,8-8S34.411,44,30,44z',
viewBox: '-5 0 65 65',
viewBox: '-5 -5 65 65',
},
[SessionIconType.Emoji]: {
path:
@ -137,6 +139,11 @@ export const icons = {
'M17.5,2.4c-1.82-1.5-4.21-2.1-6.57-1.64c-3.09,0.6-5.57,3.09-6.15,6.19c-0.4,2.1,0.04,4.21,1.22,5.95 C7.23,14.7,8,16.41,8.36,18.12c0.17,0.81,0.89,1.41,1.72,1.41h4.85c0.83,0,1.55-0.59,1.72-1.42c0.37-1.82,1.13-3.55,2.19-4.99 c1-1.36,1.53-2.96,1.53-4.65C20.37,6.11,19.32,3.9,17.5,2.4z M17.47,12.11c-1.21,1.64-2.07,3.6-2.55,5.72l-4.91-0.05 c-0.4-1.93-1.25-3.84-2.62-5.84c-0.93-1.36-1.27-3.02-0.95-4.67c0.46-2.42,2.39-4.37,4.81-4.83c0.41-0.08,0.82-0.12,1.23-0.12 c1.44,0,2.82,0.49,3.94,1.4c1.43,1.18,2.25,2.91,2.25,4.76C18.67,9.79,18.25,11.04,17.47,12.11z M15.94,20.27H9.61c-0.47,0-0.85,0.38-0.85,0.85s0.38,0.85,0.85,0.85h6.33c0.47,0,0.85-0.38,0.85-0.85 S16.41,20.27,15.94,20.27z M15.94,22.7H9.61c-0.47,0-0.85,0.38-0.85,0.85s0.38,0.85,0.85,0.85h6.33c0.47,0,0.85-0.38,0.85-0.85 S16.41,22.7,15.94,22.7z M12.5,3.28c-2.89,0-5.23,2.35-5.23,5.23c0,0.47,0.38,0.85,0.85,0.85s0.85-0.38,0.85-0.85 c0-1.95,1.59-3.53,3.54-3.53c0.47,0,0.85-0.38,0.85-0.85S12.97,3.28,12.5,3.28z',
viewBox: '0 0 25 25',
},
[SessionIconType.Lock]: {
path:
'M417.684,188.632H94.316c-9.923,0-17.965,8.042-17.965,17.965v239.532c0,7.952,5.234,14.965,12.863,17.222l161.684,47.906 c1.665,0.497,3.383,0.743,5.102,0.743c1.719,0,3.437-0.246,5.108-0.743l161.684-47.906c7.623-2.258,12.857-9.27,12.857-17.222 V206.596C435.649,196.674,427.607,188.632,417.684,188.632z M399.719,432.715L256,475.298l-143.719-42.583V224.561h287.439 V432.715z M256,0c-69.345,0-125.754,56.949-125.754,126.952v76.052h35.93v-76.052c0-50.188,40.295-91.022,89.825-91.022 s89.825,40.834,89.825,91.022v76.65h35.93v-76.65C381.754,56.949,325.339,0,256,0z M256,308.398c-9.923,0-17.965,8.042-17.965,17.965v47.906c0,9.923,8.042,17.965,17.965,17.965 c9.923,0,17.965-8.042,17.965-17.965v-47.906C273.965,316.44,265.923,308.398,256,308.398z',
viewBox: '0 0 512 512',
},
[SessionIconType.Microphone]: {
path:
'M43.362728,18.444286 C46.0752408,18.444286 48.2861946,16.2442453 48.2861946,13.5451212 L48.2861946,6.8991648 C48.2861946,4.20004074 46.0752408,2 43.362728,2 C40.6502153,2 38.4392615,4.20004074 38.4392615,6.8991648 L38.4392615,13.5451212 C38.4392615,16.249338 40.6502153,18.444286 43.362728,18.444286 Z M51.0908304,12.9238134 C51.4388509,12.9238134 51.7203381,13.2039112 51.7203381,13.5502139 C51.7203381,17.9248319 48.3066664,21.5202689 43.9871178,21.8411082 L43.9871178,21.8411082 L43.9871178,25.747199 L47.2574869,25.747199 C47.6055074,25.747199 47.8869946,26.0272968 47.8869946,26.3735995 C47.8869946,26.7199022 47.6055074,27 47.2574869,27 L47.2574869,27 L39.4628512,27 C39.1148307,27 38.8333435,26.7199022 38.8333435,26.3735995 C38.8333435,26.0272968 39.1148307,25.747199 39.4628512,25.747199 L39.4628512,25.747199 L42.7332204,25.747199 L42.7332204,21.8411082 C38.4136717,21.5253616 35,17.9248319 35,13.5502139 C35,13.2039112 35.2814872,12.9238134 35.6295077,12.9238134 C35.9775282,12.9238134 36.2538974,13.2039112 36.2436615,13.5502139 C36.2436615,17.4512121 39.4321435,20.623956 43.3524921,20.623956 C47.2728408,20.623956 50.4613228,17.4512121 50.4613228,13.5502139 C50.4613228,13.2039112 50.7428099,12.9238134 51.0908304,12.9238134 Z M43.362728,3.24770829 C45.3843177,3.24770829 47.0322972,4.88755347 47.0322972,6.8991648 L47.0322972,13.5451212 C47.0322972,15.5567325 45.3843177,17.1965777 43.362728,17.1965777 C41.3411383,17.1965777 39.6931589,15.5567325 39.6931589,13.5451212 L39.6931589,6.8991648 C39.6931589,4.88755347 41.3411383,3.24770829 43.362728,3.24770829 lZ',

View file

@ -41,6 +41,9 @@ export class SessionIcon extends React.PureComponent<Props> {
iconDimensions = '20';
break;
case SessionIconSize.Large:
iconDimensions = '25';
break;
case SessionIconSize.Huge:
iconDimensions = '30';
break;
default:

View file

@ -1,10 +1,11 @@
import React from 'react';
import classNames from 'classnames';
import Slider from 'rc-slider';
import { SessionToggle } from '../SessionToggle';
import { SessionButton } from '../SessionButton';
import { SessionSettingType } from './SessionSettings';
import { SessionRadioGroup } from '../SessionRadioGroup';
interface Props {
@ -14,17 +15,24 @@ interface Props {
value: any;
options?: Array<any>;
onClick?: any;
onSliderChange?: any;
content: any;
}
export class SessionSettingListItem extends React.Component<Props> {
interface State {
sliderValue: number | null;
}
export class SessionSettingListItem extends React.Component<Props, State> {
public static defaultProps = {
inline: true,
};
public constructor(props: Props) {
super(props);
this.state = {};
this.state = {
sliderValue: null,
};
this.handleClick = this.handleClick.bind(this);
}
@ -37,6 +45,9 @@ export class SessionSettingListItem extends React.Component<Props> {
SessionSettingType.Slider,
].includes(type);
const currentSliderValue =
type === SessionSettingType.Slider && (this.state.sliderValue || value);
return (
<div className={classNames('session-settings-item', inline && 'inline')}>
<div className="session-settings-item__info">
@ -75,6 +86,24 @@ export class SessionSettingListItem extends React.Component<Props> {
onClick={this.handleClick}
/>
)}
{type === SessionSettingType.Slider && (
<div className="slider-wrapper">
<Slider
dots={true}
step={6}
min={12}
max={96}
defaultValue={currentSliderValue}
onAfterChange={sliderValue => {
this.handleSlider(sliderValue);
}}
/>
<div className="slider-info">
<p>{`${currentSliderValue} Hours`}</p>
</div>
</div>
)}
</div>
</div>
);
@ -85,4 +114,14 @@ export class SessionSettingListItem extends React.Component<Props> {
this.props.onClick();
}
}
private handleSlider(value: any) {
if (this.props.onSliderChange) {
this.props.onSliderChange(value);
}
this.setState({
sliderValue: value,
});
}
}

View file

@ -2,6 +2,7 @@ import React from 'react';
import { SettingsHeader } from './SessionSettingsHeader';
import { SessionSettingListItem } from './SessionSettingListItem';
import { SessionButtonColor } from '../SessionButton';
export enum SessionSettingCategory {
General = 'general',
@ -19,20 +20,31 @@ export enum SessionSettingType {
Slider = 'slider',
}
//const { Settings } = window.Signal.Types;
export interface SettingsViewProps {
category: SessionSettingCategory;
}
export class SettingsView extends React.Component<SettingsViewProps> {
interface State {
hasPassword: boolean | null;
}
export class SettingsView extends React.Component<SettingsViewProps, State> {
public settingsViewRef: React.RefObject<HTMLDivElement>;
public constructor(props: any) {
super(props);
this.state = {
hasPassword: null,
};
this.settingsViewRef = React.createRef();
this.onPasswordUpdated = this.onPasswordUpdated.bind(this);
this.hasPassword();
}
/* tslint:disable-next-line:max-func-body-length */
public renderSettingInCategory() {
const { Settings } = window.Signal.Types;
@ -81,7 +93,6 @@ export class SettingsView extends React.Component<SettingsViewProps> {
setFn: window.toggleLinkPreview,
content: {},
},
{
id: 'notification-setting',
title: window.i18n('notificationSettingsDialog'),
@ -93,7 +104,7 @@ export class SettingsView extends React.Component<SettingsViewProps> {
content: {
options: {
group: 'notification-setting',
initalItem: window.getSettingValue('notification-setting'),
initalItem: window.getSettingValue('notification-setting') || 'message',
items: [
{
label: window.i18n('nameAndMessage'),
@ -115,7 +126,6 @@ export class SettingsView extends React.Component<SettingsViewProps> {
},
},
},
{
id: 'media-permissions',
title: window.i18n('mediaPermissionsTitle'),
@ -126,38 +136,131 @@ export class SettingsView extends React.Component<SettingsViewProps> {
setFn: window.toggleMediaPermissions,
content: {},
},
{
id: 'message-ttl',
title: window.i18n('messageTTL'),
description: window.i18n('messageTTLSettingDescription'),
hidden: false,
type: SessionSettingType.Slider,
category: SessionSettingCategory.Privacy,
setFn: undefined,
content: {
defaultValue: 24,
},
},
{
id: 'set-password',
title: window.i18n('setAccountPasswordTitle'),
description: window.i18n('setAccountPasswordDescription'),
hidden: this.state.hasPassword,
type: SessionSettingType.Button,
category: SessionSettingCategory.Privacy,
setFn: undefined,
content: {
buttonText: window.i18n('setPassword'),
buttonColor: SessionButtonColor.Primary,
},
onClick: () =>
window.showPasswordDialog({
action: 'set',
onSuccess: this.onPasswordUpdated,
}),
},
{
id: 'change-password',
title: window.i18n('changeAccountPasswordTitle'),
description: window.i18n('changeAccountPasswordDescription'),
hidden: !this.state.hasPassword,
type: SessionSettingType.Button,
category: SessionSettingCategory.Privacy,
setFn: undefined,
content: {
buttonText: window.i18n('changePassword'),
buttonColor: SessionButtonColor.Primary,
},
onClick: () =>
window.showPasswordDialog({
action: 'change',
onSuccess: this.onPasswordUpdated,
}),
},
{
id: 'remove-password',
title: window.i18n('removeAccountPasswordTitle'),
description: window.i18n('removeAccountPasswordDescription'),
hidden: !this.state.hasPassword,
type: SessionSettingType.Button,
category: SessionSettingCategory.Privacy,
setFn: undefined,
content: {
buttonText: window.i18n('removePassword'),
buttonColor: SessionButtonColor.Danger,
},
onClick: () =>
window.showPasswordDialog({
action: 'remove',
onSuccess: this.onPasswordUpdated,
}),
},
];
return (
<>
{localSettings.map(setting => {
const { category } = this.props;
const renderSettings = setting.category === category;
const description = setting.description || '';
const comparisonValue = setting.comparisonValue || null;
{this.state.hasPassword !== null &&
localSettings.map(setting => {
const { category } = this.props;
const content = setting.content || undefined;
const shouldRenderSettings = setting.category === category;
const description = setting.description || '';
return (
<div key={setting.id}>
{renderSettings &&
!setting.hidden && (
<SessionSettingListItem
title={setting.title}
description={description}
type={setting.type}
value={window.getSettingValue(setting.id, comparisonValue)}
onClick={() => {
this.updateSetting(setting);
}}
content={setting.content || undefined}
/>
)}
</div>
);
})}
const comparisonValue = setting.comparisonValue || null;
const value =
window.getSettingValue(setting.id, comparisonValue) ||
setting.content.defaultValue;
const sliderFn =
setting.type === SessionSettingType.Slider
? (settingValue: any) =>
window.setSettingValue(setting.id, settingValue)
: () => null;
const onClickFn =
setting.onClick ||
(() => {
this.updateSetting(setting);
});
return (
<div key={setting.id}>
{shouldRenderSettings &&
!setting.hidden && (
<SessionSettingListItem
title={setting.title}
description={description}
type={setting.type}
value={value}
onClick={onClickFn}
onSliderChange={sliderFn}
content={content}
/>
)}
</div>
);
})}
</>
);
}
public hasPassword() {
const hashPromise = window.Signal.Data.getPasswordHash();
hashPromise.then((hash: any) => {
this.setState({
hasPassword: !!hash,
});
});
}
public render() {
const { category } = this.props;
@ -180,7 +283,7 @@ export class SettingsView extends React.Component<SettingsViewProps> {
if (item.type === SessionSettingType.Toggle) {
// If no custom afterClick function given, alter values in storage here
// Switch to opposite state
const newValue = !window.getSettingValue(item.id);
const newValue = ! window.getSettingValue(item.id);
window.setSettingValue(item.id, newValue);
}
}
@ -190,4 +293,18 @@ export class SettingsView extends React.Component<SettingsViewProps> {
const selectedValue = $(`#${settingID} .session-radio input:checked`).val();
window.setSettingValue(settingID, selectedValue);
}
public onPasswordUpdated(action: string) {
if (action === 'set') {
this.setState({
hasPassword: true,
});
}
if (action === 'remove') {
this.setState({
hasPassword: false,
});
}
}
}

View file

@ -26,7 +26,7 @@ export class SettingsHeader extends React.Component<SettingsViewProps> {
{categoryTitle}
<SessionIconButton
iconType={SessionIconType.Search}
iconSize={SessionIconSize.Large}
iconSize={SessionIconSize.Huge}
onClick={this.focusSearch}
/>
</div>

10
ts/global.d.ts vendored
View file

@ -4,14 +4,19 @@ interface Window {
getAccountManager: any;
mnemonic: any;
clipboard: any;
passwordUtil: any;
userConfig: any;
dcodeIO: any;
libsignal: any;
libloki: any;
displayNameRegex: any;
Signal: any;
Whisper: any;
ConversationController: any;
setPassword: any;
textsecure: any;
Session: any;
@ -21,14 +26,19 @@ interface Window {
generateID: any;
storage: any;
pushToast: any;
confirmationDialog: any;
showSeedDialog: any;
showPasswordDialog: any;
deleteAccount: any;
toggleTheme: any;
toggleMenuBar: any;
toggleSpellCheck: any;
toggleLinkPreview: any;
toggleMediaPermissions: any;
getSettingValue: any;
setSettingValue: any;
}

5472
yarn.lock

File diff suppressed because it is too large Load diff