181 lines
5.1 KiB
TypeScript
181 lines
5.1 KiB
TypeScript
import * as followControllerActions from '../../actions/follow-controller';
|
|
import * as messages from '../../../shared/messages';
|
|
import MessageListener from '../../MessageListener';
|
|
import HintKeyProducer from '../../hint-key-producer';
|
|
|
|
import { SettingRepositoryImpl } from '../../repositories/SettingRepository';
|
|
import FollowSlaveClient, { FollowSlaveClientImpl }
|
|
from '../../client/FollowSlaveClient';
|
|
|
|
let settingRepository = new SettingRepositoryImpl();
|
|
|
|
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: Window) {
|
|
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());
|
|
}
|
|
|
|
this.broadcastMessage((c: FollowSlaveClient) => {
|
|
c.filterHints(this.state.keys!!);
|
|
});
|
|
}
|
|
|
|
activate(): void {
|
|
this.broadcastMessage((c: FollowSlaveClient) => {
|
|
c.activateIfExists(
|
|
this.state.keys!!,
|
|
this.state.newTab!!,
|
|
this.state.background!!);
|
|
});
|
|
}
|
|
|
|
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('iframe');
|
|
|
|
new FollowSlaveClientImpl(this.win).requestHintCount(
|
|
{ width: viewWidth, height: viewHeight },
|
|
{ x: 0, y: 0 });
|
|
|
|
for (let ele of Array.from(frameElements)) {
|
|
let { left: frameX, top: frameY } = ele.getBoundingClientRect();
|
|
new FollowSlaveClientImpl(ele.contentWindow!!).requestHintCount(
|
|
{ width: viewWidth, height: viewHeight },
|
|
{ x: frameX, y: frameY },
|
|
);
|
|
}
|
|
}
|
|
|
|
create(count: number, sender: Window) {
|
|
let produced = [];
|
|
for (let i = 0; i < count; ++i) {
|
|
produced.push((this.producer as HintKeyProducer).produce());
|
|
}
|
|
this.keys = this.keys.concat(produced);
|
|
|
|
let doc = this.win.document;
|
|
let viewWidth = this.win.innerWidth || doc.documentElement.clientWidth;
|
|
let viewHeight = this.win.innerHeight || doc.documentElement.clientHeight;
|
|
let pos = { x: 0, y: 0 };
|
|
if (sender !== window) {
|
|
let frameElements = this.win.document.querySelectorAll('iframe');
|
|
let ele = Array.from(frameElements).find(e => e.contentWindow === sender);
|
|
if (!ele) {
|
|
// elements of the sender is gone
|
|
return;
|
|
}
|
|
let { left: frameX, top: frameY } = ele.getBoundingClientRect();
|
|
pos = { x: frameX, y: frameY };
|
|
}
|
|
new FollowSlaveClientImpl(sender).createHints(
|
|
{ width: viewWidth, height: viewHeight },
|
|
pos,
|
|
produced,
|
|
);
|
|
}
|
|
|
|
remove() {
|
|
this.keys = [];
|
|
this.broadcastMessage((c: FollowSlaveClient) => {
|
|
c.clearHints();
|
|
});
|
|
}
|
|
|
|
private hintchars() {
|
|
return settingRepository.get().properties.hintchars;
|
|
}
|
|
|
|
private broadcastMessage(f: (clinet: FollowSlaveClient) => void) {
|
|
let windows = [window.self].concat(Array.from(window.frames as any));
|
|
windows
|
|
.map(w => new FollowSlaveClientImpl(w))
|
|
.forEach(c => f(c));
|
|
}
|
|
}
|