Implement options page (#38)
This commit is contained in:
193
src/options/interface.js
Normal file
193
src/options/interface.js
Normal file
@@ -0,0 +1,193 @@
|
||||
module.exports = Interface;
|
||||
|
||||
var m = require("mithril");
|
||||
|
||||
/**
|
||||
* Options main interface
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param object settings Settings object
|
||||
* @param function saveSettings Function to save settings
|
||||
* @return void
|
||||
*/
|
||||
function Interface(settings, saveSettings) {
|
||||
// public methods
|
||||
this.attach = attach;
|
||||
this.view = view;
|
||||
|
||||
// fields
|
||||
this.settings = settings;
|
||||
this.saveSettings = saveSettings;
|
||||
this.saveEnabled = false;
|
||||
|
||||
// 'default' store must not be displayed or later attempted to be saved
|
||||
delete this.settings.stores.default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach the interface on the given element
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param DOMElement element Target element
|
||||
* @return void
|
||||
*/
|
||||
function attach(element) {
|
||||
m.mount(element, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates vnodes for render
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param function ctl Controller
|
||||
* @param object params Runtime params
|
||||
* @return []Vnode
|
||||
*/
|
||||
function view(ctl, params) {
|
||||
var nodes = [];
|
||||
nodes.push(m("h3", "Basic settings"));
|
||||
nodes.push(createCheckbox.call(this, "autoSubmit", "Automatically submit forms after filling"));
|
||||
|
||||
nodes.push(m("h3", "Custom store locations"));
|
||||
nodes.push(
|
||||
m("div", { class: "notice" }, "(this overrides default store and $PASSWORD_STORE_DIR)")
|
||||
);
|
||||
for (var storeId in this.settings.stores) {
|
||||
nodes.push(createCustomStore.call(this, storeId));
|
||||
}
|
||||
nodes.push(
|
||||
m(
|
||||
"a.add-store",
|
||||
{
|
||||
onclick: () => {
|
||||
addEmptyStore(this.settings.stores);
|
||||
this.saveEnabled = true;
|
||||
}
|
||||
},
|
||||
"Add store"
|
||||
)
|
||||
);
|
||||
|
||||
if (typeof this.error !== "undefined") {
|
||||
nodes.push(m("div.error", this.error.message));
|
||||
}
|
||||
|
||||
nodes.push(
|
||||
m(
|
||||
"button.save",
|
||||
{
|
||||
disabled: !this.saveEnabled,
|
||||
onclick: async () => {
|
||||
try {
|
||||
await this.saveSettings(this.settings);
|
||||
this.error = undefined;
|
||||
} catch (e) {
|
||||
this.error = e;
|
||||
}
|
||||
this.saveEnabled = false;
|
||||
m.redraw();
|
||||
}
|
||||
},
|
||||
"Save"
|
||||
)
|
||||
);
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates vnode for a checkbox setting
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param string key Settings key
|
||||
* @param string title Label for the checkbox
|
||||
* @return Vnode
|
||||
*/
|
||||
function createCheckbox(key, title) {
|
||||
return m("div.option", { class: key }, [
|
||||
m("label", [
|
||||
m("input[type=checkbox]", {
|
||||
title: title,
|
||||
checked: this.settings[key],
|
||||
onchange: e => {
|
||||
this.settings[key] = e.target.checked;
|
||||
this.saveEnabled = true;
|
||||
}
|
||||
}),
|
||||
title
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates vnode for a custom store configuration
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param string storeId Store ID
|
||||
* @return Vnode
|
||||
*/
|
||||
function createCustomStore(storeId) {
|
||||
let store = this.settings.stores[storeId];
|
||||
|
||||
return m("div.option.custom-store", { class: "store-" + store.name }, [
|
||||
m("input[type=text].name", {
|
||||
title: "The name for this password store",
|
||||
value: store.name,
|
||||
placeholder: "name",
|
||||
onchange: e => {
|
||||
store.name = e.target.value;
|
||||
this.saveEnabled = true;
|
||||
}
|
||||
}),
|
||||
m("input[type=text].path", {
|
||||
title: "The full path to this password store",
|
||||
value: store.path,
|
||||
placeholder: "/path/to/store",
|
||||
onchange: e => {
|
||||
store.path = e.target.value;
|
||||
this.saveEnabled = true;
|
||||
}
|
||||
}),
|
||||
m(
|
||||
"a.remove",
|
||||
{
|
||||
title: "Remove this password store",
|
||||
onclick: () => {
|
||||
delete this.settings.stores[storeId];
|
||||
this.saveEnabled = true;
|
||||
}
|
||||
},
|
||||
"[X]"
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates new store ID
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return string new store ID
|
||||
*/
|
||||
function newId() {
|
||||
return Math.random()
|
||||
.toString(36)
|
||||
.substr(2, 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new empty store
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param []object stores List of stores to add a new store to
|
||||
* @return void
|
||||
*/
|
||||
function addEmptyStore(stores) {
|
||||
let store = { id: newId(), name: "", path: "" };
|
||||
stores[store.id] = store;
|
||||
}
|
Reference in New Issue
Block a user