Follow as a clean architecture
This commit is contained in:
parent
5b7f7f5dbd
commit
e0c4182f14
11 changed files with 493 additions and 10 deletions
21
src/content/controllers/FollowKeyController.ts
Normal file
21
src/content/controllers/FollowKeyController.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import FollowSlaveUseCase from '../usecases/FollowSlaveUseCase';
|
||||||
|
import Key from '../domains/Key';
|
||||||
|
|
||||||
|
export default class FollowKeyController {
|
||||||
|
private followSlaveUseCase: FollowSlaveUseCase;
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
followSlaveUseCase = new FollowSlaveUseCase(),
|
||||||
|
} = {}) {
|
||||||
|
this.followSlaveUseCase = followSlaveUseCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
press(key: Key): boolean {
|
||||||
|
if (!this.followSlaveUseCase.isFollowMode()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.followSlaveUseCase.sendKey(key);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
31
src/content/controllers/FollowMasterController.ts
Normal file
31
src/content/controllers/FollowMasterController.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import FollowMasterUseCase from '../usecases/FollowMasterUseCase';
|
||||||
|
import * as messages from '../../shared/messages';
|
||||||
|
|
||||||
|
export default class FollowMasterController {
|
||||||
|
private followMasterUseCase: FollowMasterUseCase;
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
followMasterUseCase = new FollowMasterUseCase(),
|
||||||
|
} = {}) {
|
||||||
|
this.followMasterUseCase = followMasterUseCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
followStart(m: messages.FollowStartMessage): void {
|
||||||
|
this.followMasterUseCase.startFollow(m.newTab, m.background);
|
||||||
|
}
|
||||||
|
|
||||||
|
responseCountTargets(
|
||||||
|
m: messages.FollowResponseCountTargetsMessage, sender: Window,
|
||||||
|
): void {
|
||||||
|
this.followMasterUseCase.createSlaveHints(m.count, sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPress(message: messages.FollowKeyPressMessage): void {
|
||||||
|
if (message.key === '[' && message.ctrlKey) {
|
||||||
|
this.followMasterUseCase.cancelFollow();
|
||||||
|
} else {
|
||||||
|
this.followMasterUseCase.enqueue(message.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
32
src/content/controllers/FollowSlaveController.ts
Normal file
32
src/content/controllers/FollowSlaveController.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import * as messages from '../../shared/messages';
|
||||||
|
import FollowSlaveUseCase from '../usecases/FollowSlaveUseCase';
|
||||||
|
|
||||||
|
export default class FollowSlaveController {
|
||||||
|
private usecase: FollowSlaveUseCase;
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
usecase = new FollowSlaveUseCase(),
|
||||||
|
} = {}) {
|
||||||
|
this.usecase = usecase;
|
||||||
|
}
|
||||||
|
|
||||||
|
countTargets(m: messages.FollowRequestCountTargetsMessage): void {
|
||||||
|
this.usecase.countTargets(m.viewSize, m.framePosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
createHints(m: messages.FollowCreateHintsMessage): void {
|
||||||
|
this.usecase.createHints(m.viewSize, m.framePosition, m.tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
showHints(m: messages.FollowShowHintsMessage): void {
|
||||||
|
this.usecase.showHints(m.prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
activate(m: messages.FollowActivateMessage): void {
|
||||||
|
this.usecase.activate(m.tag, m.newTab, m.background);
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(_m: messages.FollowRemoveHintsMessage) {
|
||||||
|
this.usecase.clear();
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,8 @@ import FocusUseCase from '../usecases/FocusUseCase';
|
||||||
import ClipboardUseCase from '../usecases/ClipboardUseCase';
|
import ClipboardUseCase from '../usecases/ClipboardUseCase';
|
||||||
import BackgroundClient from '../client/BackgroundClient';
|
import BackgroundClient from '../client/BackgroundClient';
|
||||||
import MarkKeyyUseCase from '../usecases/MarkKeyUseCase';
|
import MarkKeyyUseCase from '../usecases/MarkKeyUseCase';
|
||||||
|
import FollowMasterClient, { FollowMasterClientImpl }
|
||||||
|
from '../client/FollowMasterClient';
|
||||||
import Key from '../domains/Key';
|
import Key from '../domains/Key';
|
||||||
|
|
||||||
export default class KeymapController {
|
export default class KeymapController {
|
||||||
|
@ -29,6 +31,8 @@ export default class KeymapController {
|
||||||
|
|
||||||
private markKeyUseCase: MarkKeyyUseCase;
|
private markKeyUseCase: MarkKeyyUseCase;
|
||||||
|
|
||||||
|
private followMasterClient: FollowMasterClient;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
keymapUseCase = new KeymapUseCase(),
|
keymapUseCase = new KeymapUseCase(),
|
||||||
addonEnabledUseCase = new AddonEnabledUseCase(),
|
addonEnabledUseCase = new AddonEnabledUseCase(),
|
||||||
|
@ -39,6 +43,7 @@ export default class KeymapController {
|
||||||
clipbaordUseCase = new ClipboardUseCase(),
|
clipbaordUseCase = new ClipboardUseCase(),
|
||||||
backgroundClient = new BackgroundClient(),
|
backgroundClient = new BackgroundClient(),
|
||||||
markKeyUseCase = new MarkKeyyUseCase(),
|
markKeyUseCase = new MarkKeyyUseCase(),
|
||||||
|
followMasterClient = new FollowMasterClientImpl(window.top),
|
||||||
} = {}) {
|
} = {}) {
|
||||||
this.keymapUseCase = keymapUseCase;
|
this.keymapUseCase = keymapUseCase;
|
||||||
this.addonEnabledUseCase = addonEnabledUseCase;
|
this.addonEnabledUseCase = addonEnabledUseCase;
|
||||||
|
@ -49,6 +54,7 @@ export default class KeymapController {
|
||||||
this.clipbaordUseCase = clipbaordUseCase;
|
this.clipbaordUseCase = clipbaordUseCase;
|
||||||
this.backgroundClient = backgroundClient;
|
this.backgroundClient = backgroundClient;
|
||||||
this.markKeyUseCase = markKeyUseCase;
|
this.markKeyUseCase = markKeyUseCase;
|
||||||
|
this.followMasterClient = followMasterClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line complexity, max-lines-per-function
|
// eslint-disable-next-line complexity, max-lines-per-function
|
||||||
|
@ -96,13 +102,9 @@ export default class KeymapController {
|
||||||
case operations.SCROLL_END:
|
case operations.SCROLL_END:
|
||||||
this.scrollUseCase.scrollToEnd();
|
this.scrollUseCase.scrollToEnd();
|
||||||
break;
|
break;
|
||||||
// case operations.FOLLOW_START:
|
case operations.FOLLOW_START:
|
||||||
// window.top.postMessage(JSON.stringify({
|
this.followMasterClient.startFollow(op.newTab, op.background);
|
||||||
// type: messages.FOLLOW_START,
|
break;
|
||||||
// newTab: operation.newTab,
|
|
||||||
// background: operation.background,
|
|
||||||
// }), '*');
|
|
||||||
// break;
|
|
||||||
case operations.MARK_SET_PREFIX:
|
case operations.MARK_SET_PREFIX:
|
||||||
this.markKeyUseCase.enableSetMode();
|
this.markKeyUseCase.enableSetMode();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -6,6 +6,9 @@ import consoleFrameStyle from './site-style';
|
||||||
import MessageListener from './MessageListener';
|
import MessageListener from './MessageListener';
|
||||||
import FindController from './controllers/FindController';
|
import FindController from './controllers/FindController';
|
||||||
import MarkController from './controllers/MarkController';
|
import MarkController from './controllers/MarkController';
|
||||||
|
import FollowMasterController from './controllers/FollowMasterController';
|
||||||
|
import FollowSlaveController from './controllers/FollowSlaveController';
|
||||||
|
import FollowKeyController from './controllers/FollowKeyController';
|
||||||
import * as messages from '../shared/messages';
|
import * as messages from '../shared/messages';
|
||||||
import InputDriver from './InputDriver';
|
import InputDriver from './InputDriver';
|
||||||
import KeymapController from './controllers/KeymapController';
|
import KeymapController from './controllers/KeymapController';
|
||||||
|
@ -17,11 +20,14 @@ import AddonEnabledController from './controllers/AddonEnabledController';
|
||||||
|
|
||||||
// const store = newStore();
|
// const store = newStore();
|
||||||
|
|
||||||
|
let listener = new MessageListener();
|
||||||
if (window.self === window.top) {
|
if (window.self === window.top) {
|
||||||
// new TopContentComponent(window, store); // eslint-disable-line no-new
|
// new TopContentComponent(window, store); // eslint-disable-line no-new
|
||||||
|
|
||||||
let findController = new FindController();
|
let findController = new FindController();
|
||||||
new MessageListener().onWebMessage((message: messages.Message) => {
|
|
||||||
|
let followMasterController = new FollowMasterController();
|
||||||
|
listener.onWebMessage((message: messages.Message, sender: Window) => {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case messages.CONSOLE_ENTER_FIND:
|
case messages.CONSOLE_ENTER_FIND:
|
||||||
return findController.start(message);
|
return findController.start(message);
|
||||||
|
@ -32,6 +38,13 @@ if (window.self === window.top) {
|
||||||
case messages.CONSOLE_UNFOCUS:
|
case messages.CONSOLE_UNFOCUS:
|
||||||
window.focus();
|
window.focus();
|
||||||
consoleFrames.blur(window.document);
|
consoleFrames.blur(window.document);
|
||||||
|
break;
|
||||||
|
case messages.FOLLOW_START:
|
||||||
|
return followMasterController.followStart(message);
|
||||||
|
case messages.FOLLOW_RESPONSE_COUNT_TARGETS:
|
||||||
|
return followMasterController.responseCountTargets(message, sender);
|
||||||
|
case messages.FOLLOW_KEY_PRESS:
|
||||||
|
return followMasterController.keyPress(message);
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
@ -54,10 +67,28 @@ if (window.self === window.top) {
|
||||||
// new FrameContentComponent(window, store); // eslint-disable-line no-new
|
// new FrameContentComponent(window, store); // eslint-disable-line no-new
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let followSlaveController = new FollowSlaveController();
|
||||||
|
listener.onWebMessage((message: messages.Message) => {
|
||||||
|
switch (message.type) {
|
||||||
|
case messages.FOLLOW_REQUEST_COUNT_TARGETS:
|
||||||
|
return followSlaveController.countTargets(message);
|
||||||
|
case messages.FOLLOW_CREATE_HINTS:
|
||||||
|
return followSlaveController.createHints(message);
|
||||||
|
case messages.FOLLOW_SHOW_HINTS:
|
||||||
|
return followSlaveController.showHints(message);
|
||||||
|
case messages.FOLLOW_ACTIVATE:
|
||||||
|
return followSlaveController.activate(message);
|
||||||
|
case messages.FOLLOW_REMOVE_HINTS:
|
||||||
|
return followSlaveController.clear(message);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
let keymapController = new KeymapController();
|
let keymapController = new KeymapController();
|
||||||
let markKeyController = new MarkKeyController();
|
let markKeyController = new MarkKeyController();
|
||||||
|
let followKeyController = new FollowKeyController();
|
||||||
let inputDriver = new InputDriver(document.body);
|
let inputDriver = new InputDriver(document.body);
|
||||||
// inputDriver.onKey(key => followSlaveController.pressKey(key));
|
inputDriver.onKey(key => followKeyController.press(key));
|
||||||
inputDriver.onKey(key => markKeyController.press(key));
|
inputDriver.onKey(key => markKeyController.press(key));
|
||||||
inputDriver.onKey(key => keymapController.press(key));
|
inputDriver.onKey(key => keymapController.press(key));
|
||||||
|
|
||||||
|
|
35
src/content/repositories/FollowKeyRepository.ts
Normal file
35
src/content/repositories/FollowKeyRepository.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
export default interface FollowKeyRepository {
|
||||||
|
getKeys(): string[];
|
||||||
|
|
||||||
|
pushKey(key: string): void;
|
||||||
|
|
||||||
|
popKey(): void;
|
||||||
|
|
||||||
|
clearKeys(): void;
|
||||||
|
|
||||||
|
// eslint-disable-next-line semi
|
||||||
|
}
|
||||||
|
|
||||||
|
const current: {
|
||||||
|
keys: string[];
|
||||||
|
} = {
|
||||||
|
keys: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export class FollowKeyRepositoryImpl implements FollowKeyRepository {
|
||||||
|
getKeys(): string[] {
|
||||||
|
return current.keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
pushKey(key: string): void {
|
||||||
|
current.keys.push(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
popKey(): void {
|
||||||
|
current.keys.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearKeys(): void {
|
||||||
|
current.keys = [];
|
||||||
|
}
|
||||||
|
}
|
59
src/content/repositories/FollowMasterRepository.ts
Normal file
59
src/content/repositories/FollowMasterRepository.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
export default interface FollowMasterRepository {
|
||||||
|
setCurrentFollowMode(newTab: boolean, background: boolean): void;
|
||||||
|
|
||||||
|
getTags(): string[];
|
||||||
|
|
||||||
|
getTagsByPrefix(prefix: string): string[];
|
||||||
|
|
||||||
|
addTag(tag: string): void;
|
||||||
|
|
||||||
|
clearTags(): void;
|
||||||
|
|
||||||
|
getCurrentNewTabMode(): boolean;
|
||||||
|
|
||||||
|
getCurrentBackgroundMode(): boolean;
|
||||||
|
|
||||||
|
// eslint-disable-next-line semi
|
||||||
|
}
|
||||||
|
|
||||||
|
const current: {
|
||||||
|
newTab: boolean;
|
||||||
|
background: boolean;
|
||||||
|
tags: string[];
|
||||||
|
} = {
|
||||||
|
newTab: false,
|
||||||
|
background: false,
|
||||||
|
tags: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export class FollowMasterRepositoryImpl implements FollowMasterRepository {
|
||||||
|
setCurrentFollowMode(newTab: boolean, background: boolean): void {
|
||||||
|
current.newTab = newTab;
|
||||||
|
current.background = background;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTags(): string[] {
|
||||||
|
return current.tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTagsByPrefix(prefix: string): string[] {
|
||||||
|
return current.tags.filter(t => t.startsWith(prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
addTag(tag: string): void {
|
||||||
|
current.tags.push(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTags(): void {
|
||||||
|
current.tags = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentNewTabMode(): boolean {
|
||||||
|
return current.newTab;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentBackgroundMode(): boolean {
|
||||||
|
return current.background;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
31
src/content/repositories/FollowSlaveRepository.ts
Normal file
31
src/content/repositories/FollowSlaveRepository.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
export default interface FollowSlaveRepository {
|
||||||
|
enableFollowMode(): void;
|
||||||
|
|
||||||
|
disableFollowMode(): void;
|
||||||
|
|
||||||
|
isFollowMode(): boolean;
|
||||||
|
|
||||||
|
// eslint-disable-next-line semi
|
||||||
|
}
|
||||||
|
|
||||||
|
const current: {
|
||||||
|
enabled: boolean;
|
||||||
|
} = {
|
||||||
|
enabled: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export class FollowSlaveRepositoryImpl implements FollowSlaveRepository {
|
||||||
|
enableFollowMode(): void {
|
||||||
|
current.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
disableFollowMode(): void {
|
||||||
|
current.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isFollowMode(): boolean {
|
||||||
|
return current.enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
150
src/content/usecases/FollowMasterUseCase.ts
Normal file
150
src/content/usecases/FollowMasterUseCase.ts
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
import FollowKeyRepository, { FollowKeyRepositoryImpl }
|
||||||
|
from '../repositories/FollowKeyRepository';
|
||||||
|
import FollowMasterRepository, { FollowMasterRepositoryImpl }
|
||||||
|
from '../repositories/FollowMasterRepository';
|
||||||
|
import FollowSlaveClient, { FollowSlaveClientImpl }
|
||||||
|
from '../client/FollowSlaveClient';
|
||||||
|
import HintKeyProducer from '../hint-key-producer';
|
||||||
|
import SettingRepository, { SettingRepositoryImpl }
|
||||||
|
from '../repositories/SettingRepository';
|
||||||
|
|
||||||
|
export default class FollowMasterUseCase {
|
||||||
|
private followKeyRepository: FollowKeyRepository;
|
||||||
|
|
||||||
|
private followMasterRepository: FollowMasterRepository;
|
||||||
|
|
||||||
|
private settingRepository: SettingRepository;
|
||||||
|
|
||||||
|
// TODO Make repository
|
||||||
|
private producer: HintKeyProducer | null;
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
followKeyRepository = new FollowKeyRepositoryImpl(),
|
||||||
|
followMasterRepository = new FollowMasterRepositoryImpl(),
|
||||||
|
settingRepository = new SettingRepositoryImpl(),
|
||||||
|
} = {}) {
|
||||||
|
this.followKeyRepository = followKeyRepository;
|
||||||
|
this.followMasterRepository = followMasterRepository;
|
||||||
|
this.settingRepository = settingRepository;
|
||||||
|
this.producer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
startFollow(newTab: boolean, background: boolean): void {
|
||||||
|
let hintchars = this.settingRepository.get().properties.hintchars;
|
||||||
|
this.producer = new HintKeyProducer(hintchars);
|
||||||
|
|
||||||
|
this.followKeyRepository.clearKeys();
|
||||||
|
this.followMasterRepository.setCurrentFollowMode(newTab, background);
|
||||||
|
|
||||||
|
let viewWidth = window.top.innerWidth;
|
||||||
|
let viewHeight = window.top.innerHeight;
|
||||||
|
new FollowSlaveClientImpl(window.top).requestHintCount(
|
||||||
|
{ width: viewWidth, height: viewHeight },
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
);
|
||||||
|
|
||||||
|
let frameElements = window.document.querySelectorAll('iframe');
|
||||||
|
for (let i = 0; i < frameElements.length; ++i) {
|
||||||
|
let ele = frameElements[i] as HTMLFrameElement | HTMLIFrameElement;
|
||||||
|
let { left: frameX, top: frameY } = ele.getBoundingClientRect();
|
||||||
|
new FollowSlaveClientImpl(ele.contentWindow!!).requestHintCount(
|
||||||
|
{ width: viewWidth, height: viewHeight },
|
||||||
|
{ x: frameX, y: frameY },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-statements
|
||||||
|
createSlaveHints(count: number, sender: Window): void {
|
||||||
|
let produced = [];
|
||||||
|
for (let i = 0; i < count; ++i) {
|
||||||
|
let tag = this.producer!!.produce();
|
||||||
|
produced.push(tag);
|
||||||
|
this.followMasterRepository.addTag(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
let doc = window.document;
|
||||||
|
let viewWidth = window.innerWidth || doc.documentElement.clientWidth;
|
||||||
|
let viewHeight = window.innerHeight || doc.documentElement.clientHeight;
|
||||||
|
let pos = { x: 0, y: 0 };
|
||||||
|
if (sender !== window) {
|
||||||
|
let frameElements = window.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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelFollow(): void {
|
||||||
|
this.followMasterRepository.clearTags();
|
||||||
|
this.broadcastToSlaves((client) => {
|
||||||
|
client.clearHints();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
filter(prefix: string): void {
|
||||||
|
this.broadcastToSlaves((client) => {
|
||||||
|
client.filterHints(prefix);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
activate(tag: string): void {
|
||||||
|
this.followMasterRepository.clearTags();
|
||||||
|
|
||||||
|
let newTab = this.followMasterRepository.getCurrentNewTabMode();
|
||||||
|
let background = this.followMasterRepository.getCurrentBackgroundMode();
|
||||||
|
this.broadcastToSlaves((client) => {
|
||||||
|
client.activateIfExists(tag, newTab, background);
|
||||||
|
client.clearHints();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
enqueue(key: string): void {
|
||||||
|
switch (key) {
|
||||||
|
case 'Enter':
|
||||||
|
this.activate(this.getCurrentTag());
|
||||||
|
return;
|
||||||
|
case 'Esc':
|
||||||
|
this.cancelFollow();
|
||||||
|
return;
|
||||||
|
case 'Backspace':
|
||||||
|
case 'Delete':
|
||||||
|
this.followKeyRepository.popKey();
|
||||||
|
this.filter(this.getCurrentTag());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.followKeyRepository.pushKey(key);
|
||||||
|
|
||||||
|
let tag = this.getCurrentTag();
|
||||||
|
let matched = this.followMasterRepository.getTagsByPrefix(tag);
|
||||||
|
if (matched.length === 0) {
|
||||||
|
this.cancelFollow();
|
||||||
|
} else if (matched.length === 1) {
|
||||||
|
this.activate(tag);
|
||||||
|
} else {
|
||||||
|
this.filter(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private broadcastToSlaves(handler: (client: FollowSlaveClient) => void) {
|
||||||
|
let allFrames = [window.self].concat(Array.from(window.frames as any));
|
||||||
|
let clients = allFrames.map(frame => new FollowSlaveClientImpl(frame));
|
||||||
|
for (let client of clients) {
|
||||||
|
handler(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCurrentTag(): string {
|
||||||
|
return this.followKeyRepository.getKeys().join('');
|
||||||
|
}
|
||||||
|
}
|
91
src/content/usecases/FollowSlaveUseCase.ts
Normal file
91
src/content/usecases/FollowSlaveUseCase.ts
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import FollowSlaveRepository, { FollowSlaveRepositoryImpl }
|
||||||
|
from '../repositories/FollowSlaveRepository';
|
||||||
|
import FollowPresenter, { FollowPresenterImpl }
|
||||||
|
from '../presenters/FollowPresenter';
|
||||||
|
import TabsClient, { TabsClientImpl } from '../client/TabsClient';
|
||||||
|
import { LinkHint, InputHint } from '../presenters/Hint';
|
||||||
|
import FollowMasterClient, { FollowMasterClientImpl }
|
||||||
|
from '../client/FollowMasterClient';
|
||||||
|
import Key from '../domains/Key';
|
||||||
|
|
||||||
|
interface Size {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Point {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class FollowSlaveUseCase {
|
||||||
|
private presenter: FollowPresenter;
|
||||||
|
|
||||||
|
private tabsClient: TabsClient;
|
||||||
|
|
||||||
|
private followMasterClient: FollowMasterClient;
|
||||||
|
|
||||||
|
private followSlaveRepository: FollowSlaveRepository;
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
presenter = new FollowPresenterImpl(),
|
||||||
|
tabsClient = new TabsClientImpl(),
|
||||||
|
followMasterClient = new FollowMasterClientImpl(window.top),
|
||||||
|
followSlaveRepository = new FollowSlaveRepositoryImpl(),
|
||||||
|
} = {}) {
|
||||||
|
this.presenter = presenter;
|
||||||
|
this.tabsClient = tabsClient;
|
||||||
|
this.followMasterClient = followMasterClient;
|
||||||
|
this.followSlaveRepository = followSlaveRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
countTargets(viewSize: Size, framePosition: Point): void {
|
||||||
|
let count = this.presenter.getTargetCount(viewSize, framePosition);
|
||||||
|
this.followMasterClient.responseHintCount(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
createHints(viewSize: Size, framePosition: Point, tags: string[]): void {
|
||||||
|
this.followSlaveRepository.enableFollowMode();
|
||||||
|
this.presenter.createHints(viewSize, framePosition, tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
showHints(prefix: string) {
|
||||||
|
this.presenter.filterHints(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendKey(key: Key): void {
|
||||||
|
this.followMasterClient.sendKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
isFollowMode(): boolean {
|
||||||
|
return this.followSlaveRepository.isFollowMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
async activate(tag: string, newTab: boolean, background: boolean) {
|
||||||
|
let hint = this.presenter.getHint(tag);
|
||||||
|
if (!hint) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hint instanceof LinkHint) {
|
||||||
|
let url = hint.getLink();
|
||||||
|
// ignore taget='_blank'
|
||||||
|
if (!newTab && hint.getLinkTarget() === '_blank') {
|
||||||
|
hint.click();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-script-url
|
||||||
|
if (!url || url === '#' || url.toLowerCase().startsWith('javascript:')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.tabsClient.openUrl(url, newTab, background);
|
||||||
|
} else if (hint instanceof InputHint) {
|
||||||
|
hint.activate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(): void {
|
||||||
|
this.followSlaveRepository.disableFollowMode();
|
||||||
|
this.presenter.clearHints();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import InputDriver from '../../src/content/InputDriver';
|
import InputDriver from '../../src/content/InputDriver';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { Key } from '../../src/shared/utils/keys';
|
import Key from '../../src/content/domains/Key';
|
||||||
|
|
||||||
describe('InputDriver', () => {
|
describe('InputDriver', () => {
|
||||||
let target: HTMLElement;
|
let target: HTMLElement;
|
||||||
|
|
Reference in a new issue