From ccbe08cf66e16084c919f0b2fa2da81258c01d41 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 25 May 2019 21:33:33 +0900 Subject: [PATCH] Repeat last operation --- .../controllers/OperationController.ts | 19 ++++++- .../repositories/RepeatRepository.ts | 22 +++++++++ src/background/usecases/RepeatUseCase.ts | 49 +++++++++++++++++++ ...BackgroundClient.ts => OperationClient.ts} | 8 +-- src/content/controllers/KeymapController.ts | 6 ++- src/content/di.ts | 2 + src/shared/operations.ts | 11 ++++- 7 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 src/background/repositories/RepeatRepository.ts create mode 100644 src/background/usecases/RepeatUseCase.ts rename src/content/client/{BackgroundClient.ts => OperationClient.ts} (63%) diff --git a/src/background/controllers/OperationController.ts b/src/background/controllers/OperationController.ts index 1e80f54..7233c0e 100644 --- a/src/background/controllers/OperationController.ts +++ b/src/background/controllers/OperationController.ts @@ -6,6 +6,7 @@ import TabUseCase from '../usecases/TabUseCase'; import TabSelectUseCase from '../usecases/TabSelectUseCase'; import ZoomUseCase from '../usecases/ZoomUseCase'; import NavigateUseCase from '../usecases/NavigateUseCase'; +import RepeatUseCase from '../usecases/RepeatUseCase'; @injectable() export default class OperationController { @@ -16,11 +17,19 @@ export default class OperationController { private tabSelectUseCase: TabSelectUseCase, private zoomUseCase: ZoomUseCase, private navigateUseCase: NavigateUseCase, + private repeatUseCase: RepeatUseCase, ) { } + async exec(op: operations.Operation): Promise { + await this.doOperation(op); + if (this.repeatUseCase.isRepeatable(op)) { + this.repeatUseCase.storeLastOperation(op); + } + } + // eslint-disable-next-line complexity, max-lines-per-function - exec(operation: operations.Operation): Promise { + doOperation(operation: operations.Operation): Promise { switch (operation.type) { case operations.TAB_CLOSE: return this.tabUseCase.close(false); @@ -88,6 +97,14 @@ export default class OperationController { return this.navigateUseCase.openParent(); case operations.NAVIGATE_ROOT: return this.navigateUseCase.openRoot(); + case operations.REPEAT_LAST: + { + let last = this.repeatUseCase.getLastOperation(); + if (typeof last !== 'undefined') { + return this.doOperation(last); + } + return Promise.resolve(); + } } throw new Error('unknown operation: ' + operation.type); } diff --git a/src/background/repositories/RepeatRepository.ts b/src/background/repositories/RepeatRepository.ts new file mode 100644 index 0000000..c7f7a71 --- /dev/null +++ b/src/background/repositories/RepeatRepository.ts @@ -0,0 +1,22 @@ +import { injectable } from 'tsyringe'; +import { Operation } from '../../shared/operations'; +import MemoryStorage from '../infrastructures/MemoryStorage'; + +const REPEAT_KEY = 'repeat'; + +@injectable() +export default class RepeatRepository { + private cache: MemoryStorage; + + constructor() { + this.cache = new MemoryStorage(); + } + + getLastOperation(): Operation | undefined { + return this.cache.get(REPEAT_KEY); + } + + setLastOperation(op: Operation): void { + this.cache.set(REPEAT_KEY, op); + } +} diff --git a/src/background/usecases/RepeatUseCase.ts b/src/background/usecases/RepeatUseCase.ts new file mode 100644 index 0000000..a005682 --- /dev/null +++ b/src/background/usecases/RepeatUseCase.ts @@ -0,0 +1,49 @@ +import { injectable } from 'tsyringe'; +import * as operations from '../../shared/operations'; +import RepeatRepository from '../repositories/RepeatRepository'; + +type Operation = operations.Operation; + +@injectable() +export default class RepeatUseCase { + constructor( + private repeatRepository: RepeatRepository, + ) { + } + + storeLastOperation(op: Operation): void { + this.repeatRepository.setLastOperation(op); + } + + getLastOperation(): operations.Operation | undefined { + return this.repeatRepository.getLastOperation(); + } + + // eslint-disable-next-line complexity + isRepeatable(op: Operation): boolean { + switch (op.type) { + case operations.NAVIGATE_HISTORY_PREV: + case operations.NAVIGATE_HISTORY_NEXT: + case operations.NAVIGATE_LINK_PREV: + case operations.NAVIGATE_LINK_NEXT: + case operations.NAVIGATE_PARENT: + case operations.NAVIGATE_ROOT: + case operations.PAGE_SOURCE: + case operations.PAGE_HOME: + case operations.TAB_CLOSE: + case operations.TAB_CLOSE_FORCE: + case operations.TAB_CLOSE_RIGHT: + case operations.TAB_REOPEN: + case operations.TAB_RELOAD: + case operations.TAB_PIN: + case operations.TAB_UNPIN: + case operations.TAB_TOGGLE_PINNED: + case operations.TAB_DUPLICATE: + case operations.ZOOM_IN: + case operations.ZOOM_OUT: + case operations.ZOOM_NEUTRAL: + return true; + } + return false; + } +} diff --git a/src/content/client/BackgroundClient.ts b/src/content/client/OperationClient.ts similarity index 63% rename from src/content/client/BackgroundClient.ts rename to src/content/client/OperationClient.ts index 4a41184..d699fec 100644 --- a/src/content/client/BackgroundClient.ts +++ b/src/content/client/OperationClient.ts @@ -1,9 +1,11 @@ -import { injectable } from 'tsyringe'; import * as operations from '../../shared/operations'; import * as messages from '../../shared/messages'; -@injectable() -export default class BackgroundClient { +export default interface OperationClient { + execBackgroundOp(op: operations.Operation): Promise; +} + +export class OperationClientImpl implements OperationClient { execBackgroundOp(op: operations.Operation): Promise { return browser.runtime.sendMessage({ type: messages.BACKGROUND_OPERATION, diff --git a/src/content/controllers/KeymapController.ts b/src/content/controllers/KeymapController.ts index 4eb6955..fcfaff1 100644 --- a/src/content/controllers/KeymapController.ts +++ b/src/content/controllers/KeymapController.ts @@ -6,7 +6,7 @@ import FindSlaveUseCase from '../usecases/FindSlaveUseCase'; import ScrollUseCase from '../usecases/ScrollUseCase'; import FocusUseCase from '../usecases/FocusUseCase'; import ClipboardUseCase from '../usecases/ClipboardUseCase'; -import BackgroundClient from '../client/BackgroundClient'; +import OperationClient from '../client/OperationClient'; import MarkKeyyUseCase from '../usecases/MarkKeyUseCase'; import FollowMasterClient from '../client/FollowMasterClient'; import Key from '../domains/Key'; @@ -20,9 +20,11 @@ export default class KeymapController { private scrollUseCase: ScrollUseCase, private focusUseCase: FocusUseCase, private clipbaordUseCase: ClipboardUseCase, - private backgroundClient: BackgroundClient, private markKeyUseCase: MarkKeyyUseCase, + @inject('OperationClient') + private backgroundClient: OperationClient, + @inject('FollowMasterClient') private followMasterClient: FollowMasterClient, ) { diff --git a/src/content/di.ts b/src/content/di.ts index 23be027..e18806a 100644 --- a/src/content/di.ts +++ b/src/content/di.ts @@ -21,6 +21,7 @@ import { MarkClientImpl } from './client/MarkClient'; import { MarkKeyRepositoryImpl } from './repositories/MarkKeyRepository'; import { MarkRepositoryImpl } from './repositories/MarkRepository'; import { NavigationPresenterImpl } from './presenters/NavigationPresenter'; +import { OperationClientImpl } from './client/OperationClient'; import { ScrollPresenterImpl } from './presenters/ScrollPresenter'; import { SettingClientImpl } from './client/SettingClient'; import { SettingRepositoryImpl } from './repositories/SettingRepository'; @@ -48,6 +49,7 @@ container.register('MarkClient', { useClass: MarkClientImpl }); container.register('MarkKeyRepository', { useClass: MarkKeyRepositoryImpl }); container.register('MarkRepository', { useClass: MarkRepositoryImpl }); container.register('NavigationPresenter', { useClass: NavigationPresenterImpl }); +container.register('OperationClient', { useClass: OperationClientImpl }); container.register('ScrollPresenter', { useClass: ScrollPresenterImpl }); container.register('SettingClient', { useClass: SettingClientImpl }); container.register('SettingRepository', { useClass: SettingRepositoryImpl }); diff --git a/src/shared/operations.ts b/src/shared/operations.ts index 688c240..0f0d0c0 100644 --- a/src/shared/operations.ts +++ b/src/shared/operations.ts @@ -75,6 +75,9 @@ export const FIND_PREV = 'find.prev'; export const MARK_SET_PREFIX = 'mark.set.prefix'; export const MARK_JUMP_PREFIX = 'mark.jump.prefix'; +// Repeat +export const REPEAT_LAST = 'repeat.last'; + export interface CancelOperation { type: typeof CANCEL; } @@ -291,6 +294,10 @@ export interface MarkJumpPrefixOperation { type: typeof MARK_JUMP_PREFIX; } +export interface RepeatLastOperation { + type: typeof REPEAT_LAST; +} + export type Operation = CancelOperation | AddonEnableOperation | @@ -342,7 +349,8 @@ export type Operation = FindNextOperation | FindPrevOperation | MarkSetPrefixOperation | - MarkJumpPrefixOperation; + MarkJumpPrefixOperation | + RepeatLastOperation; const assertOptionalBoolean = (obj: any, name: string) => { if (Object.prototype.hasOwnProperty.call(obj, name) && @@ -441,6 +449,7 @@ export const valueOf = (o: any): Operation => { case FIND_PREV: case MARK_SET_PREFIX: case MARK_JUMP_PREFIX: + case REPEAT_LAST: return { type: o.type }; } throw new TypeError('unknown operation type: ' + o.type);