diff options
author | Shin'ya Ueoka <ueokande@i-beam.org> | 2019-05-07 21:16:47 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-07 21:16:47 +0900 |
commit | 05ef6a8ca35aaa801c11eb6b4896caa3690058af (patch) | |
tree | 2c7708ca91ac2b462cc86aa28612e3d3943496f3 /src/content/components/top-content/follow-controller.ts | |
parent | 457d954e08923b4accd28a919c72d0b61db1bb98 (diff) | |
parent | 27d0a7f37d24a0ad68a8ccb7dee18fc1d00eea58 (diff) |
Merge pull request #578 from ueokande/move-to-typescript
Move to TypeScript
Diffstat (limited to 'src/content/components/top-content/follow-controller.ts')
-rw-r--r-- | src/content/components/top-content/follow-controller.ts | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/src/content/components/top-content/follow-controller.ts b/src/content/components/top-content/follow-controller.ts new file mode 100644 index 0000000..d49b22a --- /dev/null +++ b/src/content/components/top-content/follow-controller.ts @@ -0,0 +1,166 @@ +import * as followControllerActions from '../../actions/follow-controller'; +import * as messages from '../../../shared/messages'; +import MessageListener, { WebMessageSender } from '../../MessageListener'; +import HintKeyProducer from '../../hint-key-producer'; + +const broadcastMessage = (win: Window, message: messages.Message): void => { + let json = JSON.stringify(message); + let frames = [win.self].concat(Array.from(win.frames as any)); + frames.forEach(frame => frame.postMessage(json, '*')); +}; + +export default class FollowController { + private win: Window; + + private store: any; + + private state: { + enabled?: boolean; + newTab?: boolean; + background?: boolean; + keys?: string, + }; + + private keys: string[]; + + private producer: HintKeyProducer | null; + + constructor(win: Window, store: any) { + this.win = win; + this.store = store; + this.state = {}; + this.keys = []; + this.producer = null; + + new MessageListener().onWebMessage(this.onMessage.bind(this)); + + store.subscribe(() => { + this.update(); + }); + } + + onMessage(message: messages.Message, sender: WebMessageSender) { + switch (message.type) { + case messages.FOLLOW_START: + return this.store.dispatch( + followControllerActions.enable(message.newTab, message.background)); + case messages.FOLLOW_RESPONSE_COUNT_TARGETS: + return this.create(message.count, sender); + case messages.FOLLOW_KEY_PRESS: + return this.keyPress(message.key, message.ctrlKey); + } + } + + update(): void { + let prevState = this.state; + this.state = this.store.getState().followController; + + 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(): void { + let shown = this.keys.filter((key) => { + return key.startsWith(this.state.keys as string); + }); + if (shown.length === 1) { + this.activate(); + this.store.dispatch(followControllerActions.disable()); + } + + broadcastMessage(this.win, { + type: messages.FOLLOW_SHOW_HINTS, + keys: this.state.keys as string, + }); + } + + activate(): void { + broadcastMessage(this.win, { + type: messages.FOLLOW_ACTIVATE, + keys: this.state.keys as string, + }); + } + + keyPress(key: string, ctrlKey: boolean): boolean { + if (key === '[' && ctrlKey) { + this.store.dispatch(followControllerActions.disable()); + return true; + } + switch (key) { + case 'Enter': + this.activate(); + this.store.dispatch(followControllerActions.disable()); + break; + case 'Esc': + this.store.dispatch(followControllerActions.disable()); + break; + case 'Backspace': + case 'Delete': + this.store.dispatch(followControllerActions.backspace()); + break; + default: + if (this.hintchars().includes(key)) { + this.store.dispatch(followControllerActions.keyPress(key)); + } + break; + } + return true; + } + + count() { + this.producer = new HintKeyProducer(this.hintchars()); + let doc = this.win.document; + let viewWidth = this.win.innerWidth || doc.documentElement.clientWidth; + let viewHeight = this.win.innerHeight || doc.documentElement.clientHeight; + let frameElements = this.win.document.querySelectorAll('frame,iframe'); + + this.win.postMessage(JSON.stringify({ + type: messages.FOLLOW_REQUEST_COUNT_TARGETS, + viewSize: { width: viewWidth, height: viewHeight }, + framePosition: { x: 0, y: 0 }, + }), '*'); + frameElements.forEach((ele) => { + let { left: frameX, top: frameY } = ele.getBoundingClientRect(); + let message = JSON.stringify({ + type: messages.FOLLOW_REQUEST_COUNT_TARGETS, + viewSize: { width: viewWidth, height: viewHeight }, + framePosition: { x: frameX, y: frameY }, + }); + if (ele instanceof HTMLFrameElement && ele.contentWindow || + ele instanceof HTMLIFrameElement && ele.contentWindow) { + ele.contentWindow.postMessage(message, '*'); + } + }); + } + + create(count: number, sender: WebMessageSender) { + let produced = []; + for (let i = 0; i < count; ++i) { + produced.push((this.producer as HintKeyProducer).produce()); + } + this.keys = this.keys.concat(produced); + + (sender as Window).postMessage(JSON.stringify({ + type: messages.FOLLOW_CREATE_HINTS, + keysArray: produced, + newTab: this.state.newTab, + background: this.state.background, + }), '*'); + } + + remove() { + this.keys = []; + broadcastMessage(this.win, { + type: messages.FOLLOW_REMOVE_HINTS, + }); + } + + hintchars() { + return this.store.getState().setting.properties.hintchars; + } +} |