diff --git a/lib/tooltip.js b/lib/tooltip.js index 0bab929..7c668cc 100644 --- a/lib/tooltip.js +++ b/lib/tooltip.js @@ -50,7 +50,6 @@ Tooltip.prototype.init = function init(element, options) { this.enabled = true this.element = element this.options = this.getOptions(options) - // this.disposables = new EventKit.CompositeDisposable() if (this.options.viewport) { if (typeof this.options.viewport === 'function') { diff --git a/lib/views/components/setting-checkbox.js b/lib/views/components/setting-checkbox.js new file mode 100644 index 0000000..dfecbb6 --- /dev/null +++ b/lib/views/components/setting-checkbox.js @@ -0,0 +1,35 @@ +'use strict' + +const h = require('virtual-dom/h') +const inherits = require('util').inherits +const Base = require('vdelement') + +module.exports = Checkbox + +function Checkbox(target) { + if (!(this instanceof Checkbox)) + return new Checkbox(target) + + Base.call(this, target) +} +inherits(Checkbox, Base) + +Checkbox.prototype.render = function render(opts) { + return h('.form-group', [ + h('.checkbox', [ + h('label', [ + h('input', { + type: 'checkbox' + , id: opts.id + , checked: this.target.settings.get(opts.id) + , onchange: (e) => { + this.target.settings.set(opts.id, e.target.checked) + this.target.needsLayout() + } + }) + , h('.setting-title', ` ${opts.title}`) + , h('p.form-control-static', opts.note) + ]) + ]) + ]) +} diff --git a/lib/views/components/setting-colorpicker.js b/lib/views/components/setting-colorpicker.js new file mode 100644 index 0000000..5032d9a --- /dev/null +++ b/lib/views/components/setting-colorpicker.js @@ -0,0 +1,43 @@ +'use strict' + +const h = require('virtual-dom/h') +const inherits = require('util').inherits +const Base = require('vdelement') + +module.exports = ColorPicker + +function ColorPicker(target) { + if (!(this instanceof ColorPicker)) + return new ColorPicker(target) + + Base.call(this, target) +} +inherits(ColorPicker, Base) + +ColorPicker.prototype.render = function render(opts) { + const id = opts.id + const val = this.target.settings.get(id) + + return h('.form-group', [ + h('label.control-label', { + for: id + }, opts.title) + , h('.input-group', [ + h('span.input-group-addon', { + style: { + backgroundColor: val + } + }) + , h('input.form-control', { + type: 'text' + , id: id + , onkeyup: (e) => { + this.target.settings.set(id, e.target.value) + this.target.needsLayout() + } + , value: val + }) + ]) + , h('p.form-control-static', opts.note) + ]) +} diff --git a/lib/views/components/setting-theme.js b/lib/views/components/setting-theme.js new file mode 100644 index 0000000..a0a658f --- /dev/null +++ b/lib/views/components/setting-theme.js @@ -0,0 +1,59 @@ +'use strict' + +const h = require('virtual-dom/h') +const inherits = require('util').inherits +const Base = require('vdelement') + +module.exports = ThemeSelect + +function ThemeSelect(target) { + if (!(this instanceof ThemeSelect)) + return new ThemeSelect(target) + + Base.call(this, target) +} +inherits(ThemeSelect, Base) + +ThemeSelect.prototype.render = function render(opts) { + const id = opts.id + + const settings = this.target.settings + const themes = this.target.themes.themes + const items = new Array(themes.size) + let i = 0 + for (const item of themes.values()) { + items[i++] = h('option', { + selected: item.active + }, item.name) + } + + return h('.form-group', [ + h('label.control-label', { + attributes: { + for: id + } + }, opts.title) + , h('select.form-control', { + onchange: (e) => { + this.target.themes.activate(e.target.value) + } + }, items) + , h('p.form-control-static', [ + opts.note + , h('button.btn.btn-link', { + onclick: (e) => { + e.target.blur() + const currentTheme = settings.get('theme.active') + this.target.themes.load(currentTheme, () => { + this.target.needsLayout() + }) + return false + } + , tabindex: '-1' + , attributes: { + tabindex: '-1' + } + }, 'Reload') + ]) + ]) +} diff --git a/lib/views/settings.js b/lib/views/settings.js index e9f0089..1144820 100644 --- a/lib/views/settings.js +++ b/lib/views/settings.js @@ -8,6 +8,40 @@ const path = require('path') const HOME = process.env.EYEARESEE_HOME const THEMES_DIR = path.join(HOME, 'themes') +// Components +const Checkbox = require('./components/setting-checkbox') +const ColorPicker = require('./components/setting-colorpicker') +const ThemeSelect = require('./components/setting-theme') + +const SETTINGS = [ + { title: 'Settings', type: 'title' } +, { id: 'user.color' + , title: 'Nickname Color' + , note: 'Set the color of your nickname in all chats' + , type: 'colorpicker' + } +, { id: 'theme.active' + , title: 'Theme' + , note: `To add a new theme, place a .css file in ${THEMES_DIR}. ` + , type: 'themeselect' + } +, { id: 'sounds.enabled' + , title: 'Play Sounds' + , note: 'Sounds will be played for things like receiving a new message.' + , type: 'checkbox' + } +, { id: 'invites.accept.auto' + , title: 'Auto Accept Invites' + , note: 'Auto join channel when invited.' + , type: 'checkbox' + } +, { id: 'userbar.hidden' + , title: 'Hide Userbar' + , note: 'The Userbar is normally shown when viewing a Channel.' + , type: 'checkbox' + } +] + module.exports = Settings function Settings(target) { @@ -15,6 +49,10 @@ function Settings(target) { return new Settings(target) Base.call(this, target) + + this.checkbox = new Checkbox(target) + this.colorpicker = new ColorPicker(target) + this.themeselect = new ThemeSelect(target) } inherits(Settings, Base) @@ -23,20 +61,6 @@ Settings.prototype.close = function close(e) { this.target.router.goto('/connection') } -Settings.prototype.onPlaySoundsChange = function onPlaySoundsChange(e) { - debug('playSounds changed') - const checked = e.target.checked - this.target.settings.set('sounds.enabled', checked) - this.target.needsLayout() -} - -Settings.prototype.onAutoAcceptInvite = function onAutoAcceptInvite(e) { - debug('autoAcceptInvites changed') - const checked = e.target.checked - this.target.settings.set('invites.accept.auto', checked) - this.target.needsLayout() -} - Settings.prototype.onChangeTheme = function onChangeTheme(e) { debug('changed to %s', e.target.value) this.target.themes.activate(e.target.value) @@ -51,32 +75,17 @@ Settings.prototype.reloadThemes = function reloadThemes(e) { e.target.blur() } -Settings.prototype.onUserColorChanged = function onUserColorChanged(e) { - const val = e.target.value - debug('user.color changed to %s', val) - this.target.settings.set('user.color', val) - this.target.needsLayout() -} - -const notes = { - playSounds: 'Sounds will be played for things like receiving' + - ' a new message.' -, autoAcceptInvites: 'Auto join channel when invited.' -, userColor: 'Set the color of your nickname in all chats' -} - Settings.prototype.render = function render() { - const settings = this.target.settings - const themes = new Array(this.target.themes.themes.size) - let i = 0 - for (const item of this.target.themes.themes.values()) { - themes[i++] = h('option', { - selected: item.active - }, item.name) + const items = new Array(SETTINGS.length) + + for (const item of SETTINGS) { + if (item.type === 'title') { + items.push(h('h3.form-title', item.title)) + } else { + items.push(this[item.type].render(item)) + } } - const note = `To add a new theme, place a .css file in ${THEMES_DIR}. ` - return h('irc-settings.settings-container', [ h('a.close', { innerHTML: '×' @@ -86,82 +95,7 @@ Settings.prototype.render = function render() { }) , h('.form.form-dark.col-sm-8', [ h('.clearfix') - , h('form.form', [ - h('h3.form-title', 'Settings') - , h('.form-group', [ - h('label.control-label', { - attributes: { - for: 'theme' - } - }, 'Theme') - , h('select.form-control', { - onchange: (e) => { - this.onChangeTheme(e) - } - }, themes) - , h('p.form-control-static', [ - note - , h('button.btn.btn-link', { - onclick: (e) => { - this.reloadThemes(e) - return false - } - , tabindex: '-1' - , attributes: { - tabindex: '-1' - } - }, 'Reload') - ]) - ]) - , checkbox('playSounds', (e) => { - this.onPlaySoundsChange(e) - }, ' Play Sounds', settings, notes.playSounds) - , checkbox('autoAcceptInvites', (e) => { - this.onAutoAcceptInvite(e) - }, ' Auto Accept Invites', settings, notes.autoAcceptInvites) - , colorPicker('user.color', (e) => { - this.onUserColorChanged(e) - }, 'Nickname Color', settings, notes.userColor) - ]) - ]) - ]) -} - -function checkbox(id, onchange, title, settings, note) { - return h('.form-group', [ - h('.checkbox', [ - h('label', [ - h('input', { - type: 'checkbox' - , id: id - , checked: settings.get(id) - , onchange: onchange - }) - , h('.setting-title', title) - , h('p.form-control-static', note) - ]) - ]) - ]) -} - -function colorPicker(id, onkeyup, title, settings, note) { - return h('.form-group', [ - h('label.control-label', { - for: id - }, title) - , h('.input-group', [ - h('span.input-group-addon', { - style: { - backgroundColor: settings.get(id) - } - }) - , h('input.form-control', { - type: 'text' - , id: id - , onkeyup: onkeyup - , value: settings.get(id) - }) + , h('form.form', items) ]) - , h('p.form-control-static', note) ]) } diff --git a/test/views/components/setting-checkbox.js b/test/views/components/setting-checkbox.js new file mode 100644 index 0000000..c2e86bd --- /dev/null +++ b/test/views/components/setting-checkbox.js @@ -0,0 +1,54 @@ +'use strict' + +const test = require('tap').test +const View = require('../../../lib/views/components/setting-checkbox') +const IRC = require('eyearesee-client') +const Settings = IRC.Settings + +test('SettingCheckbox', (t) => { + const defs = new Map([[ 'test', true ]]) + const target = { + needsLayout: () => { + t.pass('called target.needsLayout') + } + , settings: new Settings(defs) + } + + const view = View(target) + + target.settings.on('settingChanged', (key, orig, val) => { + t.equal(key, 'test', 'key') + t.equal(orig, undefined, 'old value') + t.equal(val, false, 'new value') + }) + + const out = view.render({ + id: 'test' + , title: 'test' + , note: 'NOTE' + }) + + t.equal(out.children.length, 1, 'children') + let kid = out.children[0] + t.equal(kid.children.length, 1, 'children') + kid = kid.children[0] + t.equal(kid.children.length, 3, 'children') + + const input = kid.children[0] + t.equal(input.tagName, 'INPUT', 'tagName') + t.equal(input.properties.checked, true, 'checked') + + input.properties.onchange({ + target: { + checked: false + } + }) + + const title = kid.children[1] + t.equal(title.properties.className, 'setting-title', 'className') + + const note = kid.children[2] + t.equal(note.properties.className, 'form-control-static', 'className') + + t.end() +}) diff --git a/test/views/components/setting-colorpicker.js b/test/views/components/setting-colorpicker.js new file mode 100644 index 0000000..b6b4200 --- /dev/null +++ b/test/views/components/setting-colorpicker.js @@ -0,0 +1,51 @@ +'use strict' + +const test = require('tap').test +const View = require('../../../lib/views/components/setting-colorpicker') +const IRC = require('eyearesee-client') +const Settings = IRC.Settings + +test('SettingColorPicker', (t) => { + const defs = new Map([[ 'color', '#fff' ]]) + const target = { + needsLayout: () => { + t.pass('called target.needsLayout') + } + , settings: new Settings(defs) + } + + const view = View(target) + + target.settings.on('settingChanged', (key, orig, val) => { + t.equal(key, 'color', 'key') + t.equal(orig, undefined, 'old value') + t.equal(val, '#000', 'new value') + }) + + const out = view.render({ + id: 'color' + , title: 'test' + , note: 'NOTE' + }) + + t.equal(out.children.length, 3, 'children') + const label = out.children[0] + t.equal(label.children.length, 1, 'children') + + const inputGroup = out.children[1] + t.equal(inputGroup.children.length, 2, 'children') + + const input = inputGroup.children[1] + t.equal(input.properties.value.value, '#fff', 'value') + + input.properties.onkeyup({ + target: { + value: '#000' + } + }) + + const note = out.children[2] + t.equal(note.children.length, 1, 'children') + + t.end() +})