Follow as a clean architecture
This commit is contained in:
parent
5b7f7f5dbd
commit
e0c4182f14
11 changed files with 493 additions and 10 deletions
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();
|
||||
}
|
||||
}
|
Reference in a new issue