/**
* @module web/SettingsView
*/
import log from 'loglevel';
import {Collapse} from 'bootstrap';
/**
*@class This class handles the rendering of
* and interaction with the settings.
*
* @constructor
*/
class SettingsView {
/**
* {number} aniTime - set time to show/hide elements
*/
#aniTime = 250;
/**
* Constructor
*/
constructor() {
this.__togglePaddingType('NONE');
this.__togglePaddingCharType('FIXED');
this.__toggleSeparatorType('NONE');
$('#invalidSettings').hide();
$('#padding_type').on('change', (e) => {
this.__togglePaddingType(e);
});
$('#padding_character_type').on('change', (e) => {
this.__togglePaddingCharType(e);
});
$('#separator_type').on('change', (e) => {
this.__toggleSeparatorType(e);
});
};
/**
* Update the fields in the settings with
* the contents of the current preset
*
* @param {Object} preset - settings belonging by the current preset
*/
renderSettings(preset) {
this.resetConfigError();
// get the keys of the preset
const keys = Object.keys(preset);
// update all fields
keys.forEach((key) => {
$(`#${key}`).val(preset[key]);
});
// hide everything that should not be visible
this.__toggleSeparatorType(preset.separator_type);
this.__togglePaddingType(preset.padding_type);
this.__togglePaddingCharType(preset.padding_character_type);
};
/**
* Bind the form to the event handler.
*
* A change in any field of the form will trigger the form
* validation. If the field input is not valid, an error message will be shown
* and the Generate button will be disabled.
* If the field input is valid the modified settings will be saved and the
* Generate button will be enabled.
*
* Save the modified settings to generate passwords
* based on these new settings
*
* @param {function} handle - pass control to the Controller
*/
bindSaveSettings(handle) {
/*
* Set an event handler on a change in any field and if the input is
* valid, process and save the settings.
* This removes the need for a 'save' button.
*/
$('form#passwordSettings').on('keyup change', (e) => {
const form = e.target.form;
e.preventDefault();
e.stopPropagation();
log.trace('starting validity checks.');
// remove the green marks
form.classList.remove('was-validated');
this.setErrorMessage('');
// check if the form is valid and if not, show the error message
if (!form.reportValidity()) {
// the form is not valid
form.classList.add('was-validated');
this.setErrorMessage('Please fix the invalid input before generating passwords.');
this.disableGenerateButton(true);
}
else {
// get the form data and pass it on to the controller handle function
const formData = new FormData(form);
const data = {};
[...formData.keys()].forEach((key) => {
const values = formData.getAll(key);
data[key] = (values.length > 1) ? values : values.join();
});
// True MVC requires this to be handled by the PasswordView,
// but since it's only one line, we don't bother
$('#generate').prop('disabled', false);
log.trace(JSON.stringify(data));
handle(data);
}
});
}
/**
* Open the accordion to show the settings
*/
showSettings() {
// make sure the settings section is not collapsed
Collapse.getOrCreateInstance('#collapseSettings', {
toggle: false,
}).show();
}
/**
* disable the Generate button to avoid errors in password generation
* @param {boolean} state - if true then disable the button
*/
disableGenerateButton(state) {
// True MVC requires this to be handled by the PasswordView,
// but since it's only one line, we don't bother
$('#generate').prop('disabled', (state));
}
/**
* Set the error message and show it
* or hide it on empty message
*
* @param {string }t - text or empty
*/
setErrorMessage(t) {
if (t && t.length > 0) {
$('#invalidSettings').text(t).show();
}
else {
$('#invalidSettings').text('').hide();
}
}
/**
* Render the error caused by the imported configuration
* Hide the fields because they cannot be updated
* Disable the Generate button because behaviour is unpredictable
*
* @param {Error} e - error object with the message to display
*/
renderConfigError(e) {
this.showSettings();
this.disableGenerateButton(true);
this.setErrorMessage(e.message);
$('#passwordSettings').hide();
}
/**
* Reset the error indications
*/
resetConfigError() {
this.setErrorMessage('');
this.disableGenerateButton(false);
$('#passwordSettings').show();
}
/**
* Toggle visibility of separator type related
* elements
*
* @private
*
* @param {Event | string } e - either the
* event or the type value
*/
__toggleSeparatorType = (e) => {
const separatorType = (typeof e == 'string') ? e : $(e.target).val();
log.trace(`__toggleCharSeparatorType: ${separatorType}`);
// always remove it, just add it only in case of 'RANDOM'
const separatorCharacter = $('#separator_character');
separatorCharacter.prop('required', false);
// always remove it, just add it only in case of 'FIXED'
const separatorAlphabet = $('#separator_alphabet');
separatorAlphabet.prop('required', false);
const separatorCharacterParent = separatorCharacter.parent();
const separatorAlphabetParent = separatorAlphabet.parent();
switch(separatorType) {
case 'NONE':
separatorCharacterParent.hide(this.#aniTime);
if (separatorCharacter.val().length < 1) {
separatorCharacter.val('#');
}
separatorAlphabetParent.hide(this.#aniTime);
if (separatorAlphabet.val().length < 2) {
separatorAlphabet.val('#+');
}
break;
case 'FIXED':
separatorCharacterParent.show(this.#aniTime);
separatorAlphabetParent.hide(this.#aniTime);
separatorCharacter.prop('required', true);
if (separatorAlphabet.val().length < 2) {
separatorAlphabet.val('#+');
}
break;
case 'RANDOM':
separatorCharacterParent.hide(this.#aniTime);
if (separatorCharacter.val().length < 1) {
separatorCharacter.val('#');
}
separatorAlphabetParent.show(this.#aniTime);
separatorAlphabet.prop('required', true);
break;
default:
const msg = `WARNING - Received invalid separator_type (${separatorType})`;
log.warn(msg);
throw (new Error(e));
}
};
/**
* Toggle visibility of padding type related
* elements
*
* @private
*
* @param {Event | string } e - either the
* event or the type value
*/
__togglePaddingType = (e) => {
const paddingType = (typeof e == 'string') ? e : $(e.target).val();
log.trace(`__toggleCharPaddingType: ${paddingType}`);
const paddingCharBefore = $('#padding_characters_before');
const paddingCharAfter = $('#padding_characters_after');
const paddingCharBeforeParent = paddingCharBefore.parent().parent();
const paddingCharAfterParent = paddingCharAfter.parent().parent();
const padToLength = $('#pad_to_length').parent().parent();
const paddingCharContainer = $('div#padding_char_container');
paddingCharBefore.prop('required', false);
paddingCharAfter.prop('required', false);
paddingCharBeforeParent.hide(this.#aniTime);
paddingCharAfterParent.hide(this.#aniTime);
padToLength.hide(this.#aniTime);
switch(paddingType) {
case 'NONE':
paddingCharContainer.hide(this.#aniTime);
break;
case 'FIXED':
paddingCharBefore.prop('required', true);
paddingCharAfter.prop('required', true);
paddingCharBeforeParent.show(this.#aniTime);
paddingCharAfterParent.show(this.#aniTime);
paddingCharContainer.show(this.#aniTime);
break;
case 'ADAPTIVE':
paddingCharBefore.val(0);
paddingCharAfter.val(0);
padToLength.show(this.#aniTime);
paddingCharContainer.show(this.#aniTime);
break;
default:
const msg = `WARNING - Received invalid padding_type=${paddingType}`;
log.warn(msg);
throw (new Error(msg));
}
};
/**
* Toggle visibility of padding type related
* elements
*
* @private
*
* @param {Event | string } e - either the
* event or the type value
*/
__togglePaddingCharType = (e) => {
const paddingType = (typeof e == 'string') ? e : $(e.target).val();
log.trace(`__togglePaddingCharType: ${paddingType}`);
// we need to hide the entire section, including the `input-group` and it's
// parent div otherwise the pad_to_length will not float next to the
// padding type element
const paddingCharacter = $('#padding_character');
const paddingAlphabet = $('#padding_alphabet');
const paddingCharacterParent = paddingCharacter.parent().parent();
const paddingAlphabetParent = paddingAlphabet.parent().parent();
// always remove it, just add it only in case of 'RANDOM'
paddingAlphabet.prop('required', false);
// always remove it, just add it only in case of 'FIXED'
paddingCharacter.prop('required', false);
// hide everything so we only show what's needed
paddingCharacterParent.hide(this.#aniTime);
paddingAlphabetParent.hide(this.#aniTime);
switch(paddingType) {
case 'NONE':
// nothing more to do here
break;
case 'FIXED':
paddingCharacterParent.show(this.#aniTime);
paddingCharacter.prop('required', true);
break;
case 'RANDOM':
paddingAlphabetParent.show(this.#aniTime);
paddingAlphabet.prop('required', true);
break;
case 'SEPARATOR':
// only allow this option be selected
// when there is a separator character,
// if not, switch to single separator char
if ($('#separator_type').val() === 'NONE') {
$('#padding_character_type').val('FIXED');
return;
}
break;
default:
const msg =
`WARNING - Received invalid padding_char_type=${paddingType}`;
log.warn(msg);
throw (new Error(msg));
}
};
}
export {SettingsView};