Use preact in console

jh-changes
Shin'ya Ueoka 6 years ago
parent 97787c773f
commit 1fb5f43305
  1. 65
      src/console/components/completion.js
  2. 175
      src/console/components/console.js
  3. 129
      src/console/components/console.jsx
  4. 10
      src/console/components/console.scss
  5. 56
      src/console/components/console/completion.jsx
  6. 32
      src/console/components/console/input.jsx
  7. 18
      src/console/components/console/message.jsx
  8. 12
      src/console/index.html
  9. 16
      src/console/index.jsx

@ -1,65 +0,0 @@
export default class Completion {
constructor(wrapper, store) {
this.wrapper = wrapper;
this.store = store;
this.prevState = {};
store.subscribe(() => {
this.update();
});
}
update() {
let state = this.store.getState();
if (JSON.stringify(this.prevState) === JSON.stringify(state)) {
return;
}
this.wrapper.innerHTML = '';
for (let i = 0; i < state.completions.length; ++i) {
let group = state.completions[i];
let title = this.createCompletionTitle(group.name);
this.wrapper.append(title);
for (let j = 0; j < group.items.length; ++j) {
let item = group.items[j];
let li = this.createCompletionItem(item.icon, item.caption, item.url);
this.wrapper.append(li);
if (i === state.groupSelection && j === state.itemSelection) {
li.classList.add('vimvixen-completion-selected');
}
}
}
this.prevState = state;
}
createCompletionTitle(text) {
let doc = this.wrapper.ownerDocument;
let li = doc.createElement('li');
li.className = 'vimvixen-console-completion-title';
li.textContent = text;
return li;
}
createCompletionItem(icon, caption, url) {
let doc = this.wrapper.ownerDocument;
let captionEle = doc.createElement('span');
captionEle.className = 'vimvixen-console-completion-item-caption';
captionEle.textContent = caption;
let urlEle = doc.createElement('span');
urlEle.className = 'vimvixen-console-completion-item-url';
urlEle.textContent = url;
let li = doc.createElement('li');
li.style.backgroundImage = 'url(' + icon + ')';
li.className = 'vimvixen-console-completion-item';
li.append(captionEle);
li.append(urlEle);
return li;
}
}

@ -1,175 +0,0 @@
import * as consoleActions from 'console/actions/console';
const inputShownMode = (state) => {
return ['command', 'find'].includes(state.mode);
};
export default class ConsoleComponent {
constructor(wrapper, store) {
this.wrapper = wrapper;
this.store = store;
this.prevMode = '';
let doc = this.wrapper.ownerDocument;
let input = doc.querySelector('#vimvixen-console-command-input');
input.addEventListener('blur', this.onBlur.bind(this));
input.addEventListener('keydown', this.onKeyDown.bind(this));
input.addEventListener('input', this.onInput.bind(this));
store.subscribe(() => {
this.update();
});
this.update();
}
onBlur() {
let state = this.store.getState();
if (state.mode === 'command' || state.mode === 'find') {
return this.store.dispatch(consoleActions.hideCommand());
}
}
doEnter(e) {
e.stopPropagation();
e.preventDefault();
let state = this.store.getState();
let value = e.target.value;
if (state.mode === 'command') {
return this.store.dispatch(consoleActions.enterCommand(value));
} else if (state.mode === 'find') {
return this.store.dispatch(consoleActions.enterFind(value));
}
}
selectNext(e) {
this.store.dispatch(consoleActions.completionNext());
e.stopPropagation();
e.preventDefault();
}
selectPrev(e) {
this.store.dispatch(consoleActions.completionPrev());
e.stopPropagation();
e.preventDefault();
}
onKeyDown(e) {
if (e.keyCode === KeyboardEvent.DOM_VK_ESCAPE && e.ctrlKey) {
this.store.dispatch(consoleActions.hideCommand());
}
switch (e.keyCode) {
case KeyboardEvent.DOM_VK_ESCAPE:
return this.store.dispatch(consoleActions.hideCommand());
case KeyboardEvent.DOM_VK_RETURN:
return this.doEnter(e);
case KeyboardEvent.DOM_VK_TAB:
if (e.shiftKey) {
this.store.dispatch(consoleActions.completionPrev());
} else {
this.store.dispatch(consoleActions.completionNext());
}
e.stopPropagation();
e.preventDefault();
break;
case KeyboardEvent.DOM_VK_OPEN_BRACKET:
if (e.ctrlKey) {
return this.store.dispatch(consoleActions.hideCommand());
}
break;
case KeyboardEvent.DOM_VK_M:
if (e.ctrlKey) {
return this.doEnter(e);
}
break;
case KeyboardEvent.DOM_VK_N:
if (e.ctrlKey) {
this.selectNext(e);
}
break;
case KeyboardEvent.DOM_VK_P:
if (e.ctrlKey) {
this.selectPrev(e);
}
break;
}
}
onInput(e) {
let state = this.store.getState();
let text = e.target.value;
this.store.dispatch(consoleActions.setConsoleText(text));
if (state.mode === 'command') {
this.store.dispatch(consoleActions.getCompletions(text));
}
}
onInputShown(state) {
let doc = this.wrapper.ownerDocument;
let input = doc.querySelector('#vimvixen-console-command-input');
input.focus();
window.focus();
if (state.mode === 'command') {
let text = state.consoleText;
input.value = text;
this.store.dispatch(consoleActions.getCompletions(text));
}
}
update() {
let state = this.store.getState();
this.updateMessage(state);
this.updateCommand(state);
this.updatePrompt(state);
if (this.prevMode !== state.mode && inputShownMode(state)) {
this.onInputShown(state);
}
this.prevMode = state.mode;
}
updateMessage(state) {
let doc = this.wrapper.ownerDocument;
let box = doc.querySelector('.vimvixen-console-message');
let display = 'none';
let classList = ['vimvixen-console-message'];
if (state.mode === 'error' || state.mode === 'info') {
display = 'block';
classList.push('vimvixen-console-' + state.mode);
}
box.className = classList.join(' ');
box.style.display = display;
box.textContent = state.messageText;
}
updateCommand(state) {
let doc = this.wrapper.ownerDocument;
let command = doc.querySelector('#vimvixen-console-command');
let input = doc.querySelector('#vimvixen-console-command-input');
let display = 'none';
if (inputShownMode(state)) {
display = 'block';
}
command.style.display = display;
input.value = state.consoleText;
}
updatePrompt(state) {
let classList = ['vimvixen-console-command-prompt'];
if (inputShownMode(state)) {
classList.push('prompt-' + state.mode);
}
let doc = this.wrapper.ownerDocument;
let ele = doc.querySelector('.vimvixen-console-command-prompt');
ele.className = classList.join(' ');
}
}

@ -0,0 +1,129 @@
import './console.scss';
import { connect } from 'preact-redux';
import { Component, h } from 'preact';
import Input from './console/input';
import Completion from './console/completion';
import Message from './console/message';
import * as consoleActions from '../../console/actions/console';
class ConsoleComponent extends Component {
onBlur() {
if (this.props.mode === 'command' || this.props.mode === 'find') {
return this.context.store.dispatch(consoleActions.hideCommand());
}
}
doEnter(e) {
e.stopPropagation();
e.preventDefault();
let value = e.target.value;
if (this.props.mode === 'command') {
return this.context.store.dispatch(consoleActions.enterCommand(value));
} else if (this.props.mode === 'find') {
return this.context.store.dispatch(consoleActions.enterFind(value));
}
}
selectNext(e) {
this.context.store.dispatch(consoleActions.completionNext());
e.stopPropagation();
e.preventDefault();
}
selectPrev(e) {
this.context.store.dispatch(consoleActions.completionPrev());
e.stopPropagation();
e.preventDefault();
}
onKeyDown(e) {
if (e.keyCode === KeyboardEvent.DOM_VK_ESCAPE && e.ctrlKey) {
this.context.store.dispatch(consoleActions.hideCommand());
}
switch (e.keyCode) {
case KeyboardEvent.DOM_VK_ESCAPE:
return this.context.store.dispatch(consoleActions.hideCommand());
case KeyboardEvent.DOM_VK_RETURN:
return this.doEnter(e);
case KeyboardEvent.DOM_VK_TAB:
if (e.shiftKey) {
this.context.store.dispatch(consoleActions.completionPrev());
} else {
this.context.store.dispatch(consoleActions.completionNext());
}
e.stopPropagation();
e.preventDefault();
break;
case KeyboardEvent.DOM_VK_OPEN_BRACKET:
if (e.ctrlKey) {
return this.context.store.dispatch(consoleActions.hideCommand());
}
break;
case KeyboardEvent.DOM_VK_M:
if (e.ctrlKey) {
return this.doEnter(e);
}
break;
case KeyboardEvent.DOM_VK_N:
if (e.ctrlKey) {
this.selectNext(e);
}
break;
case KeyboardEvent.DOM_VK_P:
if (e.ctrlKey) {
this.selectPrev(e);
}
break;
}
}
onInput(e) {
let text = e.target.value;
this.context.store.dispatch(consoleActions.setConsoleText(text));
if (this.props.mode === 'command') {
this.context.store.dispatch(consoleActions.getCompletions(text));
}
}
componentDidUpdate(prevProps) {
if (!this.input) {
return;
}
if (prevProps.mode !== 'command' && this.props.mode === 'command') {
this.context.store.dispatch(
consoleActions.getCompletions(this.props.consoleText));
this.input.focus();
} else if (prevProps.mode !== 'find' && this.props.mode === 'find') {
this.input.focus();
}
}
render() {
switch (this.props.mode) {
case 'command':
case 'find':
return <div className='vimvixen-console-command-wrapper'>
<Completion />
<Input
ref={(c) => { this.input = c; }}
mode={this.props.mode}
onBlur={this.onBlur.bind(this)}
onKeyDown={this.onKeyDown.bind(this)}
onInput={this.onInput.bind(this)}
value={this.props.consoleText}
/>
</div>;
case 'info':
case 'error':
return <Message mode={ this.props.mode } >
{ this.props.messageText }
</Message>;
}
}
}
const mapStateToProps = state => state;
export default connect(mapStateToProps)(ConsoleComponent);

@ -89,18 +89,10 @@ body {
background-color: white; background-color: white;
display: flex; display: flex;
&-prompt:before { &-prompt {
@include consoole-font; @include consoole-font;
} }
&-prompt.prompt-command:before {
content: ':';
}
&-prompt.prompt-find:before {
content: '/';
}
&-input { &-input {
border: none; border: none;
flex-grow: 1; flex-grow: 1;

@ -0,0 +1,56 @@
import { Component, h } from 'preact';
import { connect } from 'preact-redux';
const CompletionTitle = (props) => {
return <li className='vimvixen-console-completion-title' >{props.title}</li>;
};
const CompletionItem = (props) => {
let className = 'vimvixen-console-completion-item';
if (props.highlight) {
className += ' vimvixen-completion-selected';
}
return <li
className={className}
style={{ backgroundImage: 'url(' + props.icon + ')' }}
>
<span
className='vimvixen-console-completion-item-caption'
>{props.caption}</span>
<span
className='vimvixen-console-completion-item-url'
>{props.url}</span>
</li>;
};
class CompletionComponent extends Component {
render() {
let eles = [];
for (let i = 0; i < this.props.completions.length; ++i) {
let group = this.props.completions[i];
eles.push(<CompletionTitle title={ group.name }/>);
for (let j = 0; j < group.items.length; ++j) {
let item = group.items[j];
let selected =
i === this.props.groupSelection &&
j === this.props.itemSelection;
eles.push(<CompletionItem
icon={item.icon}
caption={item.caption}
url={item.url}
highlight={selected}
/ >);
}
}
return (
<ul className='vimvixen-console-completion'>
{ eles }
</ul>
);
}
}
const mapStateToProps = state => state;
export default connect(mapStateToProps)(CompletionComponent);

@ -0,0 +1,32 @@
import { Component, h } from 'preact';
export default class InputComponent extends Component {
focus() {
this.input.focus();
}
render() {
let prompt = '';
if (this.props.mode === 'command') {
prompt = ':';
} else if (this.props.mode === 'find') {
prompt = '/';
}
return (
<div className='vimvixen-console-command'>
<i className='vimvixen-console-command-prompt'>
{ prompt }
</i>
<input
className='vimvixen-console-command-input'
ref={(c) => { this.input = c; }}
onBlur={this.props.onBlur}
onKeyDown={this.props.onKeyDown}
onInput={this.props.onInput}
value={this.props.value}
/>
</div>
);
}
}

@ -0,0 +1,18 @@
import { h } from 'preact';
export default function Message(props) {
switch (props.mode) {
case 'error':
return (
<p className='vimvixen-console-message vimvixen-console-error'>
{ props.text }
</p>
);
case 'info':
return (
<p className='vimvixen-console-message vimvixen-console-info'>
{ props.children }
</p>
);
}
}

@ -5,15 +5,5 @@
<title>VimVixen console</title> <title>VimVixen console</title>
<script src='console.js'></script> <script src='console.js'></script>
</head> </head>
<body class='vimvixen-console'> <body class='vimvixen-console'></body>
<p class='vimvixen-console-message'></p>
<div id='vimvixen-console-command' class='vimvixen-console-command-wrapper'>
<ul id='vimvixen-console-completion' class='vimvixen-console-completion'></ul>
<div class='vimvixen-console-command'>
<i class='vimvixen-console-command-prompt'></i><input
id='vimvixen-console-command-input'
class='vimvixen-console-command-input'></input>
</div>
</div>
</body>
</html> </html>

@ -1,21 +1,25 @@
import './site.scss';
import messages from 'shared/messages'; import messages from 'shared/messages';
import CompletionComponent from 'console/components/completion';
import ConsoleComponent from 'console/components/console';
import reducers from 'console/reducers'; import reducers from 'console/reducers';
import { createStore, applyMiddleware } from 'redux'; import { createStore, applyMiddleware } from 'redux';
import promise from 'redux-promise'; import promise from 'redux-promise';
import * as consoleActions from 'console/actions/console'; import * as consoleActions from 'console/actions/console';
import { Provider } from 'preact-redux';
import Console from './components/console';
import { render, h } from 'preact';
const store = createStore( const store = createStore(
reducers, reducers,
applyMiddleware(promise), applyMiddleware(promise),
); );
window.addEventListener('load', () => { window.addEventListener('load', () => {
let wrapper = document.querySelector('#vimvixen-console-completion'); render(
new CompletionComponent(wrapper, store); // eslint-disable-line no-new <Provider store={store} >
new ConsoleComponent(document.body, store); // eslint-disable-line no-new <Console></Console>
</Provider>,
document.body);
}); });
const onMessage = (message) => { const onMessage = (message) => {