From ac5354020e5efdf6e284d4b36696b9f94d46bef9 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 15 Oct 2017 12:34:26 +0900 Subject: support multi-frame following --- .../components/top-content/follow-controller.js | 115 +++++++++++++++++++++ src/content/components/top-content/index.js | 32 ++++++ 2 files changed, 147 insertions(+) create mode 100644 src/content/components/top-content/follow-controller.js create mode 100644 src/content/components/top-content/index.js (limited to 'src/content/components/top-content') diff --git a/src/content/components/top-content/follow-controller.js b/src/content/components/top-content/follow-controller.js new file mode 100644 index 0000000..0474690 --- /dev/null +++ b/src/content/components/top-content/follow-controller.js @@ -0,0 +1,115 @@ +import * as followActions from 'content/actions/follow'; +import messages from 'shared/messages'; +import HintKeyProducer from 'content/hint-key-producer'; + +const DEFAULT_HINT_CHARSET = 'abcdefghijklmnopqrstuvwxyz'; + +const broadcastMessage = (win, message) => { + let json = JSON.stringify(message); + let frames = [window.self].concat(Array.from(window.frames)); + frames.forEach(frame => frame.postMessage(json, '*')); +}; + +export default class FollowController { + constructor(win, store) { + this.win = win; + this.store = store; + this.state = {}; + this.keys = []; + this.producer = null; + } + + onMessage(message, sender) { + switch (message.type) { + case messages.FOLLOW_START: + return this.store.dispatch(followActions.enable(message.newTab)); + case messages.FOLLOW_RESPONSE_COUNT_TARGETS: + return this.create(message.count, sender); + case messages.FOLLOW_KEY_PRESS: + return this.keyPress(message.key); + } + } + + update() { + let prevState = this.state; + this.state = this.store.getState().follow; + + if (!prevState.enabled && this.state.enabled) { + this.count(); + } else if (prevState.enabled && !this.state.enabled) { + this.remove(); + } else if (prevState.keys !== this.state.keys) { + this.updateHints(); + } + } + + updateHints() { + let shown = this.keys.filter(key => key.startsWith(this.state.keys)); + if (shown.length === 1) { + this.activate(); + this.store.dispatch(followActions.disable()); + } + + broadcastMessage(this.win, { + type: messages.FOLLOW_SHOW_HINTS, + keys: this.state.keys, + }); + } + + activate() { + broadcastMessage(this.win, { + type: messages.FOLLOW_ACTIVATE, + keys: this.state.keys, + }); + } + + keyPress(key) { + switch (key) { + case 'Enter': + this.activate(); + this.store.dispatch(followActions.disable()); + break; + case 'Escape': + this.store.dispatch(followActions.disable()); + break; + case 'Backspace': + case 'Delete': + this.store.dispatch(followActions.backspace()); + break; + default: + if (DEFAULT_HINT_CHARSET.includes(key)) { + this.store.dispatch(followActions.keyPress(key)); + } + break; + } + return true; + } + + count() { + this.producer = new HintKeyProducer(DEFAULT_HINT_CHARSET); + broadcastMessage(this.win, { + type: messages.FOLLOW_REQUEST_COUNT_TARGETS, + }); + } + + create(count, sender) { + let produced = []; + for (let i = 0; i < count; ++i) { + produced.push(this.producer.produce()); + } + this.keys = this.keys.concat(produced); + + sender.postMessage(JSON.stringify({ + type: messages.FOLLOW_CREATE_HINTS, + keysArray: produced, + newTab: this.state.newTab, + }), '*'); + } + + remove() { + this.keys = []; + broadcastMessage(this.win, { + type: messages.FOLLOW_REMOVE_HINTS, + }); + } +} diff --git a/src/content/components/top-content/index.js b/src/content/components/top-content/index.js new file mode 100644 index 0000000..a2179da --- /dev/null +++ b/src/content/components/top-content/index.js @@ -0,0 +1,32 @@ +import CommonComponent from '../common'; +import FollowController from './follow-controller'; +import * as consoleFrames from '../../console-frames'; +import messages from 'shared/messages'; + +export default class TopContent { + + constructor(win, store) { + this.win = win; + this.children = [ + new CommonComponent(win, store), + new FollowController(win, store), + ]; + + // TODO make component + consoleFrames.initialize(window.document); + } + + update() { + this.children.forEach(c => c.update()); + } + + onMessage(message, sender) { + switch (message.type) { + case messages.CONSOLE_HIDE_COMMAND: + this.win.focus(); + consoleFrames.blur(window.document); + return Promise.resolve(); + } + this.children.forEach(c => c.onMessage(message, sender)); + } +} -- cgit v1.2.3