console as redux architecture

jh-changes
Shin'ya Ueoka 7 years ago
parent e1c70769ea
commit 956dd937d3
  1. 14
      src/console/actions/console.js
  2. 1
      src/console/actions/index.js
  3. 131
      src/console/components/console.js
  4. 3
      src/console/index.html
  5. 19
      src/console/reducers/index.js
  6. 14
      test/console/actions/console.test.js
  7. 10
      test/console/reducers/console.test.js

@ -33,10 +33,18 @@ const hideCommand = () => {
}; };
}; };
const setCompletions = (completions) => { const setConsoleText = (consoleText) => {
return {
type: actions.CONSOLE_SET_CONSOLE_TEXT,
consoleText,
};
};
const setCompletions = (completionSource, completions) => {
return { return {
type: actions.CONSOLE_SET_COMPLETIONS, type: actions.CONSOLE_SET_COMPLETIONS,
completions: completions completionSource,
completions,
}; };
}; };
@ -53,6 +61,6 @@ const completionPrev = () => {
}; };
export { export {
showCommand, showFind, showError, showInfo, hideCommand, showCommand, showFind, showError, showInfo, hideCommand, setConsoleText,
setCompletions, completionNext, completionPrev setCompletions, completionNext, completionPrev
}; };

@ -4,6 +4,7 @@ export default {
CONSOLE_SHOW_ERROR: 'console.show.error', CONSOLE_SHOW_ERROR: 'console.show.error',
CONSOLE_SHOW_INFO: 'console.show.info', CONSOLE_SHOW_INFO: 'console.show.info',
CONSOLE_HIDE_COMMAND: 'console.hide.command', CONSOLE_HIDE_COMMAND: 'console.hide.command',
CONSOLE_SET_CONSOLE_TEXT: 'console.set.command',
CONSOLE_SET_COMPLETIONS: 'console.set.completions', CONSOLE_SET_COMPLETIONS: 'console.set.completions',
CONSOLE_COMPLETION_NEXT: 'console.completion.next', CONSOLE_COMPLETION_NEXT: 'console.completion.next',
CONSOLE_COMPLETION_PREV: 'console.completion.prev', CONSOLE_COMPLETION_PREV: 'console.completion.prev',

@ -1,25 +1,27 @@
import messages from 'shared/messages'; import messages from 'shared/messages';
import * as consoleActions from 'console/actions/console'; import * as consoleActions from 'console/actions/console';
const inputShownMode = (state) => {
return ['command', 'find'].includes(state.mode);
};
export default class ConsoleComponent { export default class ConsoleComponent {
constructor(wrapper, store) { constructor(wrapper, store) {
this.wrapper = wrapper; this.wrapper = wrapper;
this.prevState = {};
this.completionOrigin = '';
this.store = store; this.store = store;
this.prevMode = '';
let doc = this.wrapper.ownerDocument; let doc = this.wrapper.ownerDocument;
let input = doc.querySelector('#vimvixen-console-command-input'); let input = doc.querySelector('#vimvixen-console-command-input');
input.addEventListener('blur', this.onBlur.bind(this)); input.addEventListener('blur', this.onBlur.bind(this));
input.addEventListener('keydown', this.onKeyDown.bind(this)); input.addEventListener('keydown', this.onKeyDown.bind(this));
input.addEventListener('input', this.onInput.bind(this)); input.addEventListener('input', this.onInput.bind(this));
this.hideCommand();
this.hideMessage();
store.subscribe(() => { store.subscribe(() => {
this.update(); this.update();
}); });
this.update();
} }
onBlur() { onBlur() {
@ -53,105 +55,80 @@ export default class ConsoleComponent {
} }
onInput(e) { onInput(e) {
let doc = this.wrapper.ownerDocument; this.store.dispatch(consoleActions.setConsoleText(e.target.value));
let input = doc.querySelector('#vimvixen-console-command-input');
this.completionOrigin = input.value;
let source = e.target.value;
return browser.runtime.sendMessage({ return browser.runtime.sendMessage({
type: messages.CONSOLE_QUERY_COMPLETIONS, type: messages.CONSOLE_QUERY_COMPLETIONS,
text: e.target.value, text: source,
}).then((completions) => { }).then((completions) => {
this.store.dispatch(consoleActions.setCompletions(completions)); this.store.dispatch(consoleActions.setCompletions(source, completions));
}); });
} }
update() { onInputShown(state) {
let state = this.store.getState(); let doc = this.wrapper.ownerDocument;
if (this.prevState.mode !== 'command' && state.mode === 'command') { let input = doc.querySelector('#vimvixen-console-command-input');
this.showCommand(state.consoleText);
} else if (this.prevState.mode !== 'find' && state.mode === 'find') {
this.showFind();
} else if (state.mode !== 'command' && state.mode !== 'find') {
this.hideCommand();
}
if (state.mode === 'error' || state.mode === 'info') { input.focus();
this.showMessage(state.mode, state.messageText); window.focus();
} else {
this.hideMessage();
}
if (state.groupSelection >= 0 && state.itemSelection >= 0) { if (state.mode === 'command') {
let group = state.completions[state.groupSelection]; this.onInput({ target: input });
let item = group.items[state.itemSelection];
this.setCommandValue(item.content);
} else if (state.completions.length > 0 &&
JSON.stringify(this.prevState.completions) ===
JSON.stringify(state.completions)) {
// Reset input only completion groups not changed (unselected an item in
// completion) in order to avoid to override previous input
this.setCommandCompletionOrigin();
} }
this.prevState = state;
} }
showCommand(text) { update() {
this.showConsole('command', text); let state = this.store.getState();
}
this.updateMessage(state);
this.updateCommand(state);
this.updatePrompt(state);
showFind() { if (this.prevMode !== state.mode && inputShownMode(state)) {
this.showConsole('find', ''); this.onInputShown(state);
}
this.prevMode = state.mode;
} }
showConsole(mode, initial) { updateMessage(state) {
let doc = this.wrapper.ownerDocument; let doc = this.wrapper.ownerDocument;
let command = doc.querySelector('#vimvixen-console-command'); let box = doc.querySelector('.vimvixen-console-message');
let input = doc.querySelector('#vimvixen-console-command-input'); let display = 'none';
let promptEle = doc.querySelector('.vimvixen-console-command-prompt'); let classList = ['vimvixen-console-message'];
command.style.display = 'block'; if (state.mode === 'error' || state.mode === 'info') {
input.value = initial; display = 'block';
input.focus(); classList.push('vimvixen-console-' + state.mode);
promptEle.className = `vimvixen-console-command-prompt prompt-${mode}`; }
window.focus(); box.className = classList.join(' ');
this.onInput({ target: input }); box.style.display = display;
box.textContent = state.messageText;
} }
hideCommand() { updateCommand(state) {
let doc = this.wrapper.ownerDocument; let doc = this.wrapper.ownerDocument;
let command = doc.querySelector('#vimvixen-console-command'); let command = doc.querySelector('#vimvixen-console-command');
command.style.display = 'none';
}
setCommandValue(value) {
let doc = this.wrapper.ownerDocument;
let input = doc.querySelector('#vimvixen-console-command-input'); let input = doc.querySelector('#vimvixen-console-command-input');
input.value = value;
}
setCommandCompletionOrigin() { let display = 'none';
let doc = this.wrapper.ownerDocument; if (inputShownMode(state)) {
let input = doc.querySelector('#vimvixen-console-command-input'); display = 'block';
input.value = this.completionOrigin; }
}
showMessage(mode, text) { command.style.display = display;
let doc = this.wrapper.ownerDocument; input.value = state.consoleText;
let error = doc.querySelector('#vimvixen-console-message');
error.classList.remove(
'vimvixen-console-info',
'vimvixen-console-error'
);
error.classList.add('vimvixen-console-' + mode);
error.textContent = text;
error.style.display = 'block';
} }
hideMessage() { updatePrompt(state) {
let classList = ['vimvixen-console-command-prompt'];
if (inputShownMode(state)) {
classList.push('prompt-' + state.mode);
}
let doc = this.wrapper.ownerDocument; let doc = this.wrapper.ownerDocument;
let error = doc.querySelector('#vimvixen-console-message'); let ele = doc.querySelector('.vimvixen-console-command-prompt');
error.style.display = 'none'; ele.className = classList.join(' ');
} }
} }

@ -6,8 +6,7 @@
<script src='console.js'></script> <script src='console.js'></script>
</head> </head>
<body class='vimvixen-console'> <body class='vimvixen-console'>
<p id='vimvixen-console-message' <p class='vimvixen-console-message'></p>
class='vimvixen-console-message'></p>
<div id='vimvixen-console-command'> <div id='vimvixen-console-command'>
<ul id='vimvixen-console-completion' class='vimvixen-console-completion'></ul> <ul id='vimvixen-console-completion' class='vimvixen-console-completion'></ul>
<div class='vimvixen-console-command'> <div class='vimvixen-console-command'>

@ -4,6 +4,7 @@ const defaultState = {
mode: '', mode: '',
messageText: '', messageText: '',
consoleText: '', consoleText: '',
completionSource: '',
completions: [], completions: [],
groupSelection: -1, groupSelection: -1,
itemSelection: -1, itemSelection: -1,
@ -43,6 +44,13 @@ const prevSelection = (state) => {
return [state.groupSelection, state.itemSelection - 1]; return [state.groupSelection, state.itemSelection - 1];
}; };
const nextConsoleText = (completions, group, item, defaults) => {
if (group < 0 || item < 0) {
return defaults;
}
return completions[group].items[item].content;
};
export default function reducer(state = defaultState, action = {}) { export default function reducer(state = defaultState, action = {}) {
switch (action.type) { switch (action.type) {
case actions.CONSOLE_SHOW_COMMAND: case actions.CONSOLE_SHOW_COMMAND:
@ -71,9 +79,14 @@ export default function reducer(state = defaultState, action = {}) {
return Object.assign({}, state, { return Object.assign({}, state, {
mode: state.mode === 'command' || state.mode === 'find' ? '' : state.mode, mode: state.mode === 'command' || state.mode === 'find' ? '' : state.mode,
}); });
case actions.CONSOLE_SET_CONSOLE_TEXT:
return Object.assign({}, state, {
consoleText: action.consoleText,
});
case actions.CONSOLE_SET_COMPLETIONS: case actions.CONSOLE_SET_COMPLETIONS:
return Object.assign({}, state, { return Object.assign({}, state, {
completions: action.completions, completions: action.completions,
completionSource: action.completionSource,
groupSelection: -1, groupSelection: -1,
itemSelection: -1, itemSelection: -1,
}); });
@ -82,6 +95,9 @@ export default function reducer(state = defaultState, action = {}) {
return Object.assign({}, state, { return Object.assign({}, state, {
groupSelection: next[0], groupSelection: next[0],
itemSelection: next[1], itemSelection: next[1],
consoleText: nextConsoleText(
state.completions, next[0], next[1],
state.completionSource),
}); });
} }
case actions.CONSOLE_COMPLETION_PREV: { case actions.CONSOLE_COMPLETION_PREV: {
@ -89,6 +105,9 @@ export default function reducer(state = defaultState, action = {}) {
return Object.assign({}, state, { return Object.assign({}, state, {
groupSelection: next[0], groupSelection: next[0],
itemSelection: next[1], itemSelection: next[1],
consoleText: nextConsoleText(
state.completions, next[0], next[1],
state.completionSource),
}); });
} }
default: default:

@ -34,17 +34,26 @@ describe("console actions", () => {
}); });
}); });
describe("hide", () => { describe("hideCommand", () => {
it('create CONSOLE_HIDE_COMMAND action', () => { it('create CONSOLE_HIDE_COMMAND action', () => {
let action = consoleActions.hideCommand(); let action = consoleActions.hideCommand();
expect(action.type).to.equal(actions.CONSOLE_HIDE_COMMAND); expect(action.type).to.equal(actions.CONSOLE_HIDE_COMMAND);
}); });
}); });
describe('setConsoleText', () => {
it('create CONSOLE_SET_CONSOLE_TEXT action', () => {
let action = consoleActions.setConsoleText('hello world');
expect(action.type).to.equal(actions.CONSOLE_SET_CONSOLE_TEXT);
expect(action.consoleText).to.equal('hello world');
});
});
describe("setCompletions", () => { describe("setCompletions", () => {
it('create CONSOLE_SET_COMPLETIONS action', () => { it('create CONSOLE_SET_COMPLETIONS action', () => {
let action = consoleActions.setCompletions([1,2,3]); let action = consoleActions.setCompletions('query', [1, 2, 3]);
expect(action.type).to.equal(actions.CONSOLE_SET_COMPLETIONS); expect(action.type).to.equal(actions.CONSOLE_SET_COMPLETIONS);
expect(action.completionSource).to.deep.equal('query');
expect(action.completions).to.deep.equal([1, 2, 3]); expect(action.completions).to.deep.equal([1, 2, 3]);
}); });
}); });
@ -63,4 +72,3 @@ describe("console actions", () => {
}); });
}); });
}); });

@ -43,6 +43,16 @@ describe("console reducer", () => {
expect(state).to.have.property('mode', 'error'); expect(state).to.have.property('mode', 'error');
}); });
it('return next state for CONSOLE_SET_CONSOLE_TEXT', () => {
let action = {
type: actions.CONSOLE_SET_CONSOLE_TEXT,
consoleText: 'hello world'
}
let state = reducer({}, action)
expect(state).to.have.property('consoleText', 'hello world');
});
it ('return next state for CONSOLE_SET_COMPLETIONS', () => { it ('return next state for CONSOLE_SET_COMPLETIONS', () => {
let state = { let state = {
groupSelection: 0, groupSelection: 0,