You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
 
 
 

181 lines
5.1 KiB

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));
}
}