diff options
| author | Shin'ya Ueoka <ueokande@i-beam.org> | 2019-05-19 15:59:05 +0900 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-05-19 15:59:05 +0900 | 
| commit | 3f4bc62ed515f1c5da90ee1c3e42f3d435ea6e39 (patch) | |
| tree | 8af9f8e5b12d007ce9628b40f3046b73f18e29f8 /src/content/usecases | |
| parent | 6ec560bca33e774ff7e363270c423c919fdcf4ce (diff) | |
| parent | c4dcdff9844e2404e3bc035f4cea9fce2f7770ab (diff) | |
Merge pull request #587 from ueokande/refactor-content
Refactor content scripts
Diffstat (limited to 'src/content/usecases')
| -rw-r--r-- | src/content/usecases/AddonEnabledUseCase.ts | 40 | ||||
| -rw-r--r-- | src/content/usecases/ClipboardUseCase.ts | 44 | ||||
| -rw-r--r-- | src/content/usecases/ConsoleFrameUseCase.ts | 17 | ||||
| -rw-r--r-- | src/content/usecases/FindSlaveUseCase.ts | 20 | ||||
| -rw-r--r-- | src/content/usecases/FindUseCase.ts | 81 | ||||
| -rw-r--r-- | src/content/usecases/FocusUseCase.ts | 16 | ||||
| -rw-r--r-- | src/content/usecases/FollowMasterUseCase.ts | 150 | ||||
| -rw-r--r-- | src/content/usecases/FollowSlaveUseCase.ts | 91 | ||||
| -rw-r--r-- | src/content/usecases/HintKeyProducer.ts | 38 | ||||
| -rw-r--r-- | src/content/usecases/KeymapUseCase.ts | 87 | ||||
| -rw-r--r-- | src/content/usecases/MarkKeyUseCase.ts | 36 | ||||
| -rw-r--r-- | src/content/usecases/MarkUseCase.ts | 66 | ||||
| -rw-r--r-- | src/content/usecases/NavigateUseCase.ts | 36 | ||||
| -rw-r--r-- | src/content/usecases/ScrollUseCase.ts | 58 | ||||
| -rw-r--r-- | src/content/usecases/SettingUseCase.ts | 24 | 
15 files changed, 804 insertions, 0 deletions
| diff --git a/src/content/usecases/AddonEnabledUseCase.ts b/src/content/usecases/AddonEnabledUseCase.ts new file mode 100644 index 0000000..e9ce0a6 --- /dev/null +++ b/src/content/usecases/AddonEnabledUseCase.ts @@ -0,0 +1,40 @@ +import AddonIndicatorClient, { AddonIndicatorClientImpl } +  from '../client/AddonIndicatorClient'; +import AddonEnabledRepository, { AddonEnabledRepositoryImpl } +  from '../repositories/AddonEnabledRepository'; + +export default class AddonEnabledUseCase { +  private indicator: AddonIndicatorClient; + +  private repository: AddonEnabledRepository; + +  constructor({ +    indicator = new AddonIndicatorClientImpl(), +    repository = new AddonEnabledRepositoryImpl(), +  } = {}) { +    this.indicator = indicator; +    this.repository = repository; +  } + +  async enable(): Promise<void> { +    await this.setEnabled(true); +  } + +  async disable(): Promise<void> { +    await this.setEnabled(false); +  } + +  async toggle(): Promise<void> { +    let current = this.repository.get(); +    await this.setEnabled(!current); +  } + +  getEnabled(): boolean { +    return this.repository.get(); +  } + +  private async setEnabled(on: boolean): Promise<void> { +    this.repository.set(on); +    await this.indicator.setEnabled(on); +  } +} diff --git a/src/content/usecases/ClipboardUseCase.ts b/src/content/usecases/ClipboardUseCase.ts new file mode 100644 index 0000000..b2ece2f --- /dev/null +++ b/src/content/usecases/ClipboardUseCase.ts @@ -0,0 +1,44 @@ +import * as urls from '../../shared/urls'; +import ClipboardRepository, { ClipboardRepositoryImpl } +  from '../repositories/ClipboardRepository'; +import SettingRepository, { SettingRepositoryImpl } +  from '../repositories/SettingRepository'; +import TabsClient, { TabsClientImpl } +  from '../client/TabsClient'; +import ConsoleClient, { ConsoleClientImpl } from '../client/ConsoleClient'; + +export default class ClipboardUseCase { +  private repository: ClipboardRepository; + +  private settingRepository: SettingRepository; + +  private client: TabsClient; + +  private consoleClient: ConsoleClient; + +  constructor({ +    repository = new ClipboardRepositoryImpl(), +    settingRepository = new SettingRepositoryImpl(), +    client = new TabsClientImpl(), +    consoleClient = new ConsoleClientImpl(), +  } = {}) { +    this.repository = repository; +    this.settingRepository = settingRepository; +    this.client = client; +    this.consoleClient = consoleClient; +  } + +  async yankCurrentURL(): Promise<string> { +    let url = window.location.href; +    this.repository.write(url); +    await this.consoleClient.info('Yanked ' + url); +    return Promise.resolve(url); +  } + +  async openOrSearch(newTab: boolean): Promise<void> { +    let search = this.settingRepository.get().search; +    let text = this.repository.read(); +    let url = urls.searchUrl(text, search); +    await this.client.openUrl(url, newTab); +  } +} diff --git a/src/content/usecases/ConsoleFrameUseCase.ts b/src/content/usecases/ConsoleFrameUseCase.ts new file mode 100644 index 0000000..b4c756c --- /dev/null +++ b/src/content/usecases/ConsoleFrameUseCase.ts @@ -0,0 +1,17 @@ +import ConsoleFramePresenter, { ConsoleFramePresenterImpl } +  from '../presenters/ConsoleFramePresenter'; + +export default class ConsoleFrameUseCase { +  private consoleFramePresenter: ConsoleFramePresenter; + +  constructor({ +    consoleFramePresenter = new ConsoleFramePresenterImpl(), +  } = {}) { +    this.consoleFramePresenter = consoleFramePresenter; +  } + +  unfocus() { +    window.focus(); +    this.consoleFramePresenter.blur(); +  } +} diff --git a/src/content/usecases/FindSlaveUseCase.ts b/src/content/usecases/FindSlaveUseCase.ts new file mode 100644 index 0000000..b733cbd --- /dev/null +++ b/src/content/usecases/FindSlaveUseCase.ts @@ -0,0 +1,20 @@ +import FindMasterClient, { FindMasterClientImpl } +  from '../client/FindMasterClient'; + +export default class FindSlaveUseCase { +  private findMasterClient: FindMasterClient; + +  constructor({ +    findMasterClient = new FindMasterClientImpl(), +  } = {}) { +    this.findMasterClient = findMasterClient; +  } + +  findNext() { +    this.findMasterClient.findNext(); +  } + +  findPrev() { +    this.findMasterClient.findPrev(); +  } +} diff --git a/src/content/usecases/FindUseCase.ts b/src/content/usecases/FindUseCase.ts new file mode 100644 index 0000000..74cbc97 --- /dev/null +++ b/src/content/usecases/FindUseCase.ts @@ -0,0 +1,81 @@ +import FindPresenter, { FindPresenterImpl } from '../presenters/FindPresenter'; +import FindRepository, { FindRepositoryImpl } +  from '../repositories/FindRepository'; +import FindClient, { FindClientImpl } from '../client/FindClient'; +import ConsoleClient, { ConsoleClientImpl } from '../client/ConsoleClient'; + +export default class FindUseCase { +  private presenter: FindPresenter; + +  private repository: FindRepository; + +  private client: FindClient; + +  private consoleClient: ConsoleClient; + +  constructor({ +    presenter = new FindPresenterImpl() as FindPresenter, +    repository = new FindRepositoryImpl(), +    client = new FindClientImpl(), +    consoleClient = new ConsoleClientImpl(), +  } = {}) { +    this.presenter = presenter; +    this.repository = repository; +    this.client = client; +    this.consoleClient = consoleClient; +  } + +  async startFind(keyword?: string): Promise<void> { +    this.presenter.clearSelection(); +    if (keyword) { +      this.saveKeyword(keyword); +    } else { +      let lastKeyword = await this.getKeyword(); +      if (!lastKeyword) { +        return this.showNoLastKeywordError(); +      } +      this.saveKeyword(lastKeyword); +    } +    return this.findNext(); +  } + +  findNext(): Promise<void> { +    return this.findNextPrev(false); +  } + +  findPrev(): Promise<void> { +    return this.findNextPrev(true); +  } + +  private async findNextPrev( +    backwards: boolean, +  ): Promise<void> { +    let keyword = await this.getKeyword(); +    if (!keyword) { +      return this.showNoLastKeywordError(); +    } +    let found = this.presenter.find(keyword, backwards); +    if (found) { +      this.consoleClient.info('Pattern found: ' + keyword); +    } else { +      this.consoleClient.error('Pattern not found: ' + keyword); +    } +  } + +  private async getKeyword(): Promise<string | null> { +    let keyword = this.repository.getLastKeyword(); +    if (!keyword) { +      keyword = await this.client.getGlobalLastKeyword(); +    } +    return keyword; +  } + +  private async saveKeyword(keyword: string): Promise<void> { +    this.repository.setLastKeyword(keyword); +    await this.client.setGlobalLastKeyword(keyword); +  } + +  private async showNoLastKeywordError(): Promise<void> { +    await this.consoleClient.error('No previous search keywords'); +  } +} diff --git a/src/content/usecases/FocusUseCase.ts b/src/content/usecases/FocusUseCase.ts new file mode 100644 index 0000000..0ad4021 --- /dev/null +++ b/src/content/usecases/FocusUseCase.ts @@ -0,0 +1,16 @@ +import FocusPresenter, { FocusPresenterImpl } +  from '../presenters/FocusPresenter'; + +export default class FocusUseCases { +  private presenter: FocusPresenter; + +  constructor({ +    presenter = new FocusPresenterImpl(), +  } = {}) { +    this.presenter = presenter; +  } + +  focusFirstInput() { +    this.presenter.focusFirstElement(); +  } +} diff --git a/src/content/usecases/FollowMasterUseCase.ts b/src/content/usecases/FollowMasterUseCase.ts new file mode 100644 index 0000000..9cbb790 --- /dev/null +++ b/src/content/usecases/FollowMasterUseCase.ts @@ -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 './HintKeyProducer'; +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(''); +  } +} diff --git a/src/content/usecases/FollowSlaveUseCase.ts b/src/content/usecases/FollowSlaveUseCase.ts new file mode 100644 index 0000000..eb011de --- /dev/null +++ b/src/content/usecases/FollowSlaveUseCase.ts @@ -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(); +  } +} diff --git a/src/content/usecases/HintKeyProducer.ts b/src/content/usecases/HintKeyProducer.ts new file mode 100644 index 0000000..241cd56 --- /dev/null +++ b/src/content/usecases/HintKeyProducer.ts @@ -0,0 +1,38 @@ +export default class HintKeyProducer { +  private charset: string; + +  private counter: number[]; + +  constructor(charset: string) { +    if (charset.length === 0) { +      throw new TypeError('charset is empty'); +    } + +    this.charset = charset; +    this.counter = []; +  } + +  produce(): string { +    this.increment(); + +    return this.counter.map(x => this.charset[x]).join(''); +  } + +  private increment(): void { +    let max = this.charset.length - 1; +    if (this.counter.every(x => x === max)) { +      this.counter = new Array(this.counter.length + 1).fill(0); +      return; +    } + +    this.counter.reverse(); +    let len = this.charset.length; +    let num = this.counter.reduce((x, y, index) => x + y * len ** index) + 1; +    for (let i = 0; i < this.counter.length; ++i) { +      this.counter[i] = num % len; +      num = ~~(num / len); +    } +    this.counter.reverse(); +  } +} + diff --git a/src/content/usecases/KeymapUseCase.ts b/src/content/usecases/KeymapUseCase.ts new file mode 100644 index 0000000..af0ad77 --- /dev/null +++ b/src/content/usecases/KeymapUseCase.ts @@ -0,0 +1,87 @@ +import KeymapRepository, { KeymapRepositoryImpl } +  from '../repositories/KeymapRepository'; +import SettingRepository, { SettingRepositoryImpl } +  from '../repositories/SettingRepository'; +import AddonEnabledRepository, { AddonEnabledRepositoryImpl } +  from '../repositories/AddonEnabledRepository'; + +import * as operations from '../../shared/operations'; +import { Keymaps } from '../../shared/Settings'; +import Key from '../domains/Key'; +import KeySequence, * as keySequenceUtils from '../domains/KeySequence'; + +type KeymapEntityMap = Map<KeySequence, operations.Operation>; + +const reservedKeymaps: Keymaps = { +  '<Esc>': { type: operations.CANCEL }, +  '<C-[>': { type: operations.CANCEL }, +}; + + +export default class KeymapUseCase { +  private repository: KeymapRepository; + +  private settingRepository: SettingRepository; + +  private addonEnabledRepository: AddonEnabledRepository; + +  constructor({ +    repository = new KeymapRepositoryImpl(), +    settingRepository = new SettingRepositoryImpl(), +    addonEnabledRepository = new AddonEnabledRepositoryImpl(), +  } = {}) { +    this.repository = repository; +    this.settingRepository = settingRepository; +    this.addonEnabledRepository = addonEnabledRepository; +  } + +  nextOp(key: Key): operations.Operation | null { +    let sequence = this.repository.enqueueKey(key); + +    let keymaps = this.keymapEntityMap(); +    let matched = Array.from(keymaps.keys()).filter( +      (mapping: KeySequence) => { +        return mapping.startsWith(sequence); +      }); +    if (!this.addonEnabledRepository.get()) { +      // available keymaps are only ADDON_ENABLE and ADDON_TOGGLE_ENABLED if +      // the addon disabled +      matched = matched.filter((keymap) => { +        let type = (keymaps.get(keymap) as operations.Operation).type; +        return type === operations.ADDON_ENABLE || +          type === operations.ADDON_TOGGLE_ENABLED; +      }); +    } +    if (matched.length === 0) { +      // No operations to match with inputs +      this.repository.clear(); +      return null; +    } else if (matched.length > 1 || +      matched.length === 1 && sequence.length() < matched[0].length()) { +      // More than one operations are matched +      return null; +    } +    // Exactly one operation is matched +    let operation = keymaps.get(matched[0]) as operations.Operation; +    this.repository.clear(); +    return operation; +  } + +  clear(): void { +    this.repository.clear(); +  } + +  private keymapEntityMap(): KeymapEntityMap { +    let keymaps = { +      ...this.settingRepository.get().keymaps, +      ...reservedKeymaps, +    }; +    let entries = Object.entries(keymaps).map((entry) => { +      return [ +        keySequenceUtils.fromMapKeys(entry[0]), +        entry[1], +      ]; +    }) as [KeySequence, operations.Operation][]; +    return new Map<KeySequence, operations.Operation>(entries); +  } +} diff --git a/src/content/usecases/MarkKeyUseCase.ts b/src/content/usecases/MarkKeyUseCase.ts new file mode 100644 index 0000000..c0aa655 --- /dev/null +++ b/src/content/usecases/MarkKeyUseCase.ts @@ -0,0 +1,36 @@ +import MarkKeyRepository, { MarkKeyRepositoryImpl } +  from '../repositories/MarkKeyRepository'; + +export default class MarkKeyUseCase { +  private repository: MarkKeyRepository; + +  constructor({ +    repository = new MarkKeyRepositoryImpl() +  } = {}) { +    this.repository = repository; +  } + +  isSetMode(): boolean { +    return this.repository.isSetMode(); +  } + +  isJumpMode(): boolean { +    return this.repository.isJumpMode(); +  } + +  enableSetMode(): void { +    this.repository.enableSetMode(); +  } + +  disableSetMode(): void { +    this.repository.disabeSetMode(); +  } + +  enableJumpMode(): void { +    this.repository.enableJumpMode(); +  } + +  disableJumpMode(): void { +    this.repository.disabeJumpMode(); +  } +} diff --git a/src/content/usecases/MarkUseCase.ts b/src/content/usecases/MarkUseCase.ts new file mode 100644 index 0000000..530f141 --- /dev/null +++ b/src/content/usecases/MarkUseCase.ts @@ -0,0 +1,66 @@ +import ScrollPresenter, { ScrollPresenterImpl } +  from '../presenters/ScrollPresenter'; +import MarkClient, { MarkClientImpl } from '../client/MarkClient'; +import MarkRepository, { MarkRepositoryImpl } +  from '../repositories/MarkRepository'; +import SettingRepository, { SettingRepositoryImpl } +  from '../repositories/SettingRepository'; +import ConsoleClient, { ConsoleClientImpl } from '../client/ConsoleClient'; + +export default class MarkUseCase { +  private scrollPresenter: ScrollPresenter; + +  private client: MarkClient; + +  private repository: MarkRepository; + +  private settingRepository: SettingRepository; + +  private consoleClient: ConsoleClient; + +  constructor({ +    scrollPresenter = new ScrollPresenterImpl(), +    client = new MarkClientImpl(), +    repository = new MarkRepositoryImpl(), +    settingRepository = new SettingRepositoryImpl(), +    consoleClient = new ConsoleClientImpl(), +  } = {}) { +    this.scrollPresenter = scrollPresenter; +    this.client = client; +    this.repository = repository; +    this.settingRepository = settingRepository; +    this.consoleClient = consoleClient; +  } + +  async set(key: string): Promise<void> { +    let pos = this.scrollPresenter.getScroll(); +    if (this.globalKey(key)) { +      this.client.setGloablMark(key, pos); +      await this.consoleClient.info(`Set global mark to '${key}'`); +    } else { +      this.repository.set(key, pos); +      await this.consoleClient.info(`Set local mark to '${key}'`); +    } +  } + +  async jump(key: string): Promise<void> { +    if (this.globalKey(key)) { +      await this.client.jumpGlobalMark(key); +    } else { +      let pos = this.repository.get(key); +      if (!pos) { +        throw new Error('Mark is not set'); +      } +      this.scroll(pos.x, pos.y); +    } +  } + +  scroll(x: number, y: number): void { +    let smooth = this.settingRepository.get().properties.smoothscroll; +    this.scrollPresenter.scrollTo(x, y, smooth); +  } + +  private globalKey(key: string) { +    return (/^[A-Z0-9]$/).test(key); +  } +} diff --git a/src/content/usecases/NavigateUseCase.ts b/src/content/usecases/NavigateUseCase.ts new file mode 100644 index 0000000..6f82d3f --- /dev/null +++ b/src/content/usecases/NavigateUseCase.ts @@ -0,0 +1,36 @@ +import NavigationPresenter, { NavigationPresenterImpl } +  from '../presenters/NavigationPresenter'; + +export default class NavigateUseCase { +  private navigationPresenter: NavigationPresenter; + +  constructor({ +    navigationPresenter = new NavigationPresenterImpl(), +  } = {}) { +    this.navigationPresenter = navigationPresenter; +  } + +  openHistoryPrev(): void { +    this.navigationPresenter.openHistoryPrev(); +  } + +  openHistoryNext(): void { +    this.navigationPresenter.openHistoryNext(); +  } + +  openLinkPrev(): void { +    this.navigationPresenter.openLinkPrev(); +  } + +  openLinkNext(): void { +    this.navigationPresenter.openLinkNext(); +  } + +  openParent(): void { +    this.navigationPresenter.openParent(); +  } + +  openRoot(): void { +    this.navigationPresenter.openRoot(); +  } +} diff --git a/src/content/usecases/ScrollUseCase.ts b/src/content/usecases/ScrollUseCase.ts new file mode 100644 index 0000000..6a1f801 --- /dev/null +++ b/src/content/usecases/ScrollUseCase.ts @@ -0,0 +1,58 @@ +import ScrollPresenter, { ScrollPresenterImpl } +  from '../presenters/ScrollPresenter'; +import SettingRepository, { SettingRepositoryImpl } +  from '../repositories/SettingRepository'; + +export default class ScrollUseCase { +  private presenter: ScrollPresenter; + +  private settingRepository: SettingRepository; + +  constructor({ +    presenter = new ScrollPresenterImpl(), +    settingRepository = new SettingRepositoryImpl(), +  } = {}) { +    this.presenter = presenter; +    this.settingRepository = settingRepository; +  } + +  scrollVertically(count: number): void { +    let smooth = this.getSmoothScroll(); +    this.presenter.scrollVertically(count, smooth); +  } + +  scrollHorizonally(count: number): void { +    let smooth = this.getSmoothScroll(); +    this.presenter.scrollHorizonally(count, smooth); +  } + +  scrollPages(count: number): void { +    let smooth = this.getSmoothScroll(); +    this.presenter.scrollPages(count, smooth); +  } + +  scrollToTop(): void { +    let smooth = this.getSmoothScroll(); +    this.presenter.scrollToTop(smooth); +  } + +  scrollToBottom(): void { +    let smooth = this.getSmoothScroll(); +    this.presenter.scrollToBottom(smooth); +  } + +  scrollToHome(): void { +    let smooth = this.getSmoothScroll(); +    this.presenter.scrollToHome(smooth); +  } + +  scrollToEnd(): void { +    let smooth = this.getSmoothScroll(); +    this.presenter.scrollToEnd(smooth); +  } + +  private getSmoothScroll(): boolean { +    let settings = this.settingRepository.get(); +    return settings.properties.smoothscroll; +  } +} diff --git a/src/content/usecases/SettingUseCase.ts b/src/content/usecases/SettingUseCase.ts new file mode 100644 index 0000000..765cb45 --- /dev/null +++ b/src/content/usecases/SettingUseCase.ts @@ -0,0 +1,24 @@ +import SettingRepository, { SettingRepositoryImpl } +  from '../repositories/SettingRepository'; +import SettingClient, { SettingClientImpl } from '../client/SettingClient'; +import Settings from '../../shared/Settings'; + +export default class SettingUseCase { +  private repository: SettingRepository; + +  private client: SettingClient; + +  constructor({ +    repository = new SettingRepositoryImpl(), +    client = new SettingClientImpl(), +  } = {}) { +    this.repository = repository; +    this.client = client; +  } + +  async reload(): Promise<Settings> { +    let settings = await this.client.load(); +    this.repository.set(settings); +    return settings; +  } +} | 
