From ad1f3c07fbb90c4e69cc2374d74a7373e4da70f2 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 11 May 2019 11:51:29 +0900 Subject: Make scroller as a presenter --- src/content/actions/operation.ts | 17 +-- src/content/components/common/mark.ts | 9 +- src/content/components/top-content/index.ts | 5 +- src/content/presenters/FindPresenter.ts | 7 -- src/content/presenters/ScrollPresenter.ts | 179 ++++++++++++++++++++++++++++ src/content/scrolls.ts | 168 -------------------------- 6 files changed, 196 insertions(+), 189 deletions(-) create mode 100644 src/content/presenters/ScrollPresenter.ts delete mode 100644 src/content/scrolls.ts (limited to 'src') diff --git a/src/content/actions/operation.ts b/src/content/actions/operation.ts index f65d0bd..b264e36 100644 --- a/src/content/actions/operation.ts +++ b/src/content/actions/operation.ts @@ -1,7 +1,6 @@ import * as operations from '../../shared/operations'; import * as actions from './index'; import * as messages from '../../shared/messages'; -import * as scrolls from '../scrolls'; import * as navigates from '../navigates'; import * as focuses from '../focuses'; import * as urls from '../urls'; @@ -10,9 +9,11 @@ import * as markActions from './mark'; import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase'; import { SettingRepositoryImpl } from '../repositories/SettingRepository'; +import { ScrollPresenterImpl } from '../presenters/ScrollPresenter'; let addonEnabledUseCase = new AddonEnabledUseCase(); let settingRepository = new SettingRepositoryImpl(); +let scrollPresenter = new ScrollPresenterImpl(); // eslint-disable-next-line complexity, max-lines-per-function const exec = async( @@ -41,25 +42,25 @@ const exec = async( }), '*'); break; case operations.SCROLL_VERTICALLY: - scrolls.scrollVertically(operation.count, smoothscroll); + scrollPresenter.scrollVertically(operation.count, smoothscroll); break; case operations.SCROLL_HORIZONALLY: - scrolls.scrollHorizonally(operation.count, smoothscroll); + scrollPresenter.scrollHorizonally(operation.count, smoothscroll); break; case operations.SCROLL_PAGES: - scrolls.scrollPages(operation.count, smoothscroll); + scrollPresenter.scrollPages(operation.count, smoothscroll); break; case operations.SCROLL_TOP: - scrolls.scrollToTop(smoothscroll); + scrollPresenter.scrollToTop(smoothscroll); break; case operations.SCROLL_BOTTOM: - scrolls.scrollToBottom(smoothscroll); + scrollPresenter.scrollToBottom(smoothscroll); break; case operations.SCROLL_HOME: - scrolls.scrollToHome(smoothscroll); + scrollPresenter.scrollToHome(smoothscroll); break; case operations.SCROLL_END: - scrolls.scrollToEnd(smoothscroll); + scrollPresenter.scrollToEnd(smoothscroll); break; case operations.FOLLOW_START: window.top.postMessage(JSON.stringify({ diff --git a/src/content/components/common/mark.ts b/src/content/components/common/mark.ts index 77aa15d..ddd1a38 100644 --- a/src/content/components/common/mark.ts +++ b/src/content/components/common/mark.ts @@ -1,12 +1,13 @@ import * as markActions from '../../actions/mark'; -import * as scrolls from '../..//scrolls'; import * as consoleFrames from '../..//console-frames'; import * as keyUtils from '../../../shared/utils/keys'; import Mark from '../../Mark'; import { SettingRepositoryImpl } from '../../repositories/SettingRepository'; +import { ScrollPresenterImpl } from '../../presenters/ScrollPresenter'; let settingRepository = new SettingRepositoryImpl(); +let scrollPresenter = new ScrollPresenterImpl(); const cancelKey = (key: keyUtils.Key): boolean => { return key.key === 'Esc' || key.key === '[' && Boolean(key.ctrlKey); @@ -54,7 +55,7 @@ export default class MarkComponent { } doSet(key: keyUtils.Key) { - let { x, y } = scrolls.getScroll(); + let { x, y } = scrollPresenter.getScroll(); this.store.dispatch(markActions.setLocal(key.key, x, y)); } @@ -69,11 +70,11 @@ export default class MarkComponent { } let { x, y } = marks[key.key]; - scrolls.scrollTo(x, y, smoothscroll); + scrollPresenter.scrollTo(x, y, smoothscroll); } doSetGlobal(key: keyUtils.Key) { - let { x, y } = scrolls.getScroll(); + let { x, y } = scrollPresenter.getScroll(); this.store.dispatch(markActions.setGlobal(key.key, x, y)); } diff --git a/src/content/components/top-content/index.ts b/src/content/components/top-content/index.ts index b9ef2dd..de14b3f 100644 --- a/src/content/components/top-content/index.ts +++ b/src/content/components/top-content/index.ts @@ -4,10 +4,11 @@ import FindComponent from './find'; import * as consoleFrames from '../../console-frames'; import * as messages from '../../../shared/messages'; import MessageListener from '../../MessageListener'; -import * as scrolls from '../../scrolls'; import AddonEnabledUseCase from '../../usecases/AddonEnabledUseCase'; +import { ScrollPresenterImpl } from '../../presenters/ScrollPresenter'; let addonEnabledUseCase = new AddonEnabledUseCase(); +let scrollPresenter = new ScrollPresenterImpl(); export default class TopContent { private win: Window; @@ -42,7 +43,7 @@ export default class TopContent { case messages.ADDON_ENABLED_QUERY: return Promise.resolve(addonEnabled); case messages.TAB_SCROLL_TO: - return scrolls.scrollTo(message.x, message.y, false); + return scrollPresenter.scrollTo(message.x, message.y, false); } } } diff --git a/src/content/presenters/FindPresenter.ts b/src/content/presenters/FindPresenter.ts index 6dd03f8..d9bc835 100644 --- a/src/content/presenters/FindPresenter.ts +++ b/src/content/presenters/FindPresenter.ts @@ -1,4 +1,3 @@ -import ConsoleClient, { ConsoleClientImpl } from '../client/ConsoleClient'; export default interface FindPresenter { find(keyword: string, backwards: boolean): boolean; @@ -28,12 +27,6 @@ interface MyWindow extends Window { declare var window: MyWindow; export class FindPresenterImpl implements FindPresenter { - private consoleClient: ConsoleClient; - - constructor({ consoleClient = new ConsoleClientImpl() } = {}) { - this.consoleClient = consoleClient; - } - find(keyword: string, backwards: boolean): boolean { let caseSensitive = false; let wrapScan = true; diff --git a/src/content/presenters/ScrollPresenter.ts b/src/content/presenters/ScrollPresenter.ts new file mode 100644 index 0000000..9f47394 --- /dev/null +++ b/src/content/presenters/ScrollPresenter.ts @@ -0,0 +1,179 @@ +import * as doms from '../../shared/utils/dom'; + +const SCROLL_DELTA_X = 64; +const SCROLL_DELTA_Y = 64; + +// dirty way to store scrolling state on globally +let scrolling = false; +let lastTimeoutId: number | null = null; + +const isScrollableStyle = (element: Element): boolean => { + let { overflowX, overflowY } = window.getComputedStyle(element); + return !(overflowX !== 'scroll' && overflowX !== 'auto' && + overflowY !== 'scroll' && overflowY !== 'auto'); +}; + +const isOverflowed = (element: Element): boolean => { + return element.scrollWidth > element.clientWidth || + element.scrollHeight > element.clientHeight; +}; + +// Find a visiable and scrollable element by depth-first search. Currently +// this method is called by each scrolling, and the returned value of this +// method is not cached. That does not cause performance issue because in the +// most pages, the window is root element i,e, documentElement. +const findScrollable = (element: Element): Element | null => { + if (isScrollableStyle(element) && isOverflowed(element)) { + return element; + } + + let children = Array.from(element.children).filter(doms.isVisible); + for (let child of children) { + let scrollable = findScrollable(child); + if (scrollable) { + return scrollable; + } + } + return null; +}; + +const scrollTarget = () => { + if (isOverflowed(window.document.documentElement)) { + return window.document.documentElement; + } + if (isOverflowed(window.document.body)) { + return window.document.body; + } + let target = findScrollable(window.document.documentElement); + if (target) { + return target; + } + return window.document.documentElement; +}; + +const resetScrolling = () => { + scrolling = false; +}; + +class Scroller { + private element: Element; + + private smooth: boolean; + + constructor(element: Element, smooth: boolean) { + this.element = element; + this.smooth = smooth; + } + + scrollTo(x: number, y: number): void { + if (!this.smooth) { + this.element.scrollTo(x, y); + return; + } + this.element.scrollTo({ + left: x, + top: y, + behavior: 'smooth', + }); + this.prepareReset(); + } + + scrollBy(x: number, y: number): void { + let left = this.element.scrollLeft + x; + let top = this.element.scrollTop + y; + this.scrollTo(left, top); + } + + prepareReset(): void { + scrolling = true; + if (lastTimeoutId) { + clearTimeout(lastTimeoutId); + lastTimeoutId = null; + } + lastTimeoutId = setTimeout(resetScrolling, 100); + } +} + +type Point = { x: number, y: number }; + +export default interface ScrollPresenter { + getScroll(): Point; + scrollVertically(amount: number, smooth: boolean): void; + scrollHorizonally(amount: number, smooth: boolean): void; + scrollPages(amount: number, smooth: boolean): void; + scrollTo(x: number, y: number, smooth: boolean): void; + scrollToTop(smooth: boolean): void; + scrollToBottom(smooth: boolean): void; + scrollToHome(smooth: boolean): void; + scrollToEnd(smooth: boolean): void; + + // eslint-disable-next-line semi +} + +export class ScrollPresenterImpl { + getScroll(): Point { + let target = scrollTarget(); + return { x: target.scrollLeft, y: target.scrollTop }; + } + + scrollVertically(count: number, smooth: boolean): void { + let target = scrollTarget(); + let delta = SCROLL_DELTA_Y * count; + if (scrolling) { + delta = SCROLL_DELTA_Y * count * 4; + } + new Scroller(target, smooth).scrollBy(0, delta); + } + + scrollHorizonally(count: number, smooth: boolean): void { + let target = scrollTarget(); + let delta = SCROLL_DELTA_X * count; + if (scrolling) { + delta = SCROLL_DELTA_X * count * 4; + } + new Scroller(target, smooth).scrollBy(delta, 0); + } + + scrollPages(count: number, smooth: boolean): void { + let target = scrollTarget(); + let height = target.clientHeight; + let delta = height * count; + if (scrolling) { + delta = height * count; + } + new Scroller(target, smooth).scrollBy(0, delta); + } + + scrollTo(x: number, y: number, smooth: boolean): void { + let target = scrollTarget(); + new Scroller(target, smooth).scrollTo(x, y); + } + + scrollToTop(smooth: boolean): void { + let target = scrollTarget(); + let x = target.scrollLeft; + let y = 0; + new Scroller(target, smooth).scrollTo(x, y); + } + + scrollToBottom(smooth: boolean): void { + let target = scrollTarget(); + let x = target.scrollLeft; + let y = target.scrollHeight; + new Scroller(target, smooth).scrollTo(x, y); + } + + scrollToHome(smooth: boolean): void { + let target = scrollTarget(); + let x = 0; + let y = target.scrollTop; + new Scroller(target, smooth).scrollTo(x, y); + } + + scrollToEnd(smooth: boolean): void { + let target = scrollTarget(); + let x = target.scrollWidth; + let y = target.scrollTop; + new Scroller(target, smooth).scrollTo(x, y); + } +} diff --git a/src/content/scrolls.ts b/src/content/scrolls.ts deleted file mode 100644 index 6a35315..0000000 --- a/src/content/scrolls.ts +++ /dev/null @@ -1,168 +0,0 @@ -import * as doms from '../shared/utils/dom'; - -const SCROLL_DELTA_X = 64; -const SCROLL_DELTA_Y = 64; - -// dirty way to store scrolling state on globally -let scrolling = false; -let lastTimeoutId: number | null = null; - -const isScrollableStyle = (element: Element): boolean => { - let { overflowX, overflowY } = window.getComputedStyle(element); - return !(overflowX !== 'scroll' && overflowX !== 'auto' && - overflowY !== 'scroll' && overflowY !== 'auto'); -}; - -const isOverflowed = (element: Element): boolean => { - return element.scrollWidth > element.clientWidth || - element.scrollHeight > element.clientHeight; -}; - -// Find a visiable and scrollable element by depth-first search. Currently -// this method is called by each scrolling, and the returned value of this -// method is not cached. That does not cause performance issue because in the -// most pages, the window is root element i,e, documentElement. -const findScrollable = (element: Element): Element | null => { - if (isScrollableStyle(element) && isOverflowed(element)) { - return element; - } - - let children = Array.from(element.children).filter(doms.isVisible); - for (let child of children) { - let scrollable = findScrollable(child); - if (scrollable) { - return scrollable; - } - } - return null; -}; - -const scrollTarget = () => { - if (isOverflowed(window.document.documentElement)) { - return window.document.documentElement; - } - if (isOverflowed(window.document.body)) { - return window.document.body; - } - let target = findScrollable(window.document.documentElement); - if (target) { - return target; - } - return window.document.documentElement; -}; - -const resetScrolling = () => { - scrolling = false; -}; - -class Scroller { - private element: Element; - - private smooth: boolean; - - constructor(element: Element, smooth: boolean) { - this.element = element; - this.smooth = smooth; - } - - scrollTo(x: number, y: number): void { - if (!this.smooth) { - this.element.scrollTo(x, y); - return; - } - this.element.scrollTo({ - left: x, - top: y, - behavior: 'smooth', - }); - this.prepareReset(); - } - - scrollBy(x: number, y: number): void { - let left = this.element.scrollLeft + x; - let top = this.element.scrollTop + y; - this.scrollTo(left, top); - } - - prepareReset(): void { - scrolling = true; - if (lastTimeoutId) { - clearTimeout(lastTimeoutId); - lastTimeoutId = null; - } - lastTimeoutId = setTimeout(resetScrolling, 100); - } -} - -const getScroll = () => { - let target = scrollTarget(); - return { x: target.scrollLeft, y: target.scrollTop }; -}; - -const scrollVertically = (count: number, smooth: boolean): void => { - let target = scrollTarget(); - let delta = SCROLL_DELTA_Y * count; - if (scrolling) { - delta = SCROLL_DELTA_Y * count * 4; - } - new Scroller(target, smooth).scrollBy(0, delta); -}; - -const scrollHorizonally = (count: number, smooth: boolean): void => { - let target = scrollTarget(); - let delta = SCROLL_DELTA_X * count; - if (scrolling) { - delta = SCROLL_DELTA_X * count * 4; - } - new Scroller(target, smooth).scrollBy(delta, 0); -}; - -const scrollPages = (count: number, smooth: boolean): void => { - let target = scrollTarget(); - let height = target.clientHeight; - let delta = height * count; - if (scrolling) { - delta = height * count; - } - new Scroller(target, smooth).scrollBy(0, delta); -}; - -const scrollTo = (x: number, y: number, smooth: boolean): void => { - let target = scrollTarget(); - new Scroller(target, smooth).scrollTo(x, y); -}; - -const scrollToTop = (smooth: boolean): void => { - let target = scrollTarget(); - let x = target.scrollLeft; - let y = 0; - new Scroller(target, smooth).scrollTo(x, y); -}; - -const scrollToBottom = (smooth: boolean): void => { - let target = scrollTarget(); - let x = target.scrollLeft; - let y = target.scrollHeight; - new Scroller(target, smooth).scrollTo(x, y); -}; - -const scrollToHome = (smooth: boolean): void => { - let target = scrollTarget(); - let x = 0; - let y = target.scrollTop; - new Scroller(target, smooth).scrollTo(x, y); -}; - -const scrollToEnd = (smooth: boolean): void => { - let target = scrollTarget(); - let x = target.scrollWidth; - let y = target.scrollTop; - new Scroller(target, smooth).scrollTo(x, y); -}; - -export { - getScroll, - scrollVertically, scrollHorizonally, scrollPages, - scrollTo, - scrollToTop, scrollToBottom, scrollToHome, scrollToEnd -}; -- cgit v1.2.3