From 1b314a530abd404ed775db9993feea1a74992be8 Mon Sep 17 00:00:00 2001 From: Vadim Date: Sat, 17 Jul 2021 18:58:00 +0300 Subject: [PATCH 1/6] #45 : Promisify --- src/components/Carousel/Carousel.svelte | 172 +++++++++++------------- src/utils/ProgressManager.js | 39 +++--- src/utils/interval.js | 8 ++ 3 files changed, 109 insertions(+), 110 deletions(-) diff --git a/src/components/Carousel/Carousel.svelte b/src/components/Carousel/Carousel.svelte index 21d0393..c9279c7 100644 --- a/src/components/Carousel/Carousel.svelte +++ b/src/components/Carousel/Carousel.svelte @@ -13,30 +13,19 @@ } from '../../utils/event' import { getAdjacentIndexes } from '../../utils/page' import { get } from '../../utils/object' - import { ProgressManager } from '../../utils/ProgressManager.js' + import { ProgressManager } from '../../utils/ProgressManager' + import { wait } from '../../utils/interval' const dispatch = createEventDispatcher() const autoplayDirectionFnDescription = { - [NEXT]: () => { - progressManager.start(() => { - showNextPage() - }) - }, - [PREV]: () => { - progressManager.start(() => { - showPrevPage() - }) - } + [NEXT]: async () => await progressManager.start(showNextPage), + [PREV]: async () => await progressManager.start(showPrevPage) } const directionFnDescription = { - [NEXT]: () => { - showNextPage() - }, - [PREV]: () => { - showPrevPage() - } + [NEXT]: showNextPage, + [PREV]: showPrevPage } /** @@ -70,6 +59,13 @@ * Enables autoplay of pages */ export let autoplay = false + $: { + if (autoplay) { + applyAutoplay() + } else { + progressManager.reset() + } + } /** * Autoplay change interval (ms) @@ -96,31 +92,41 @@ */ export let dots = true - export function goTo(pageIndex, options) { + export async function goTo(pageIndex, options) { const animated = get(options, 'animated', true) if (typeof pageIndex !== 'number') { throw new Error('pageIndex should be a number') } - showPage(pageIndex + Number(infinite), { animated }) + await showPage(pageIndex + Number(infinite), { animated }) } - export function goToPrev(options) { + export async function goToPrev(options) { const animated = get(options, 'animated', true) - showPrevPage({ animated }) + await showPrevPage({ animated }) } - export function goToNext(options) { + export async function goToNext(options) { const animated = get(options, 'animated', true) - showNextPage({ animated }) + await showNextPage({ animated }) } let store = createStore() let currentPageIndex = 0 - $: originalCurrentPageIndex = currentPageIndex - Number(infinite); + $: originalCurrentPageIndex = getOriginalCurrentPageIndex(currentPageIndex, pagesCount, infinite) $: dispatch('pageChange', originalCurrentPageIndex) let pagesCount = 0 $: originalPagesCount = Math.max(pagesCount - (infinite ? 2 : 0), 1) // without clones + + function getOriginalCurrentPageIndex(currentPageIndex, pagesCount, infinite) { + if (infinite) { + if (currentPageIndex === pagesCount - 1) return 0 + if (currentPageIndex === 0) return pagesCount - 3 + return currentPageIndex - 1 + } + return currentPageIndex + } + let pageWidth = 0 let offset = 0 let pageWindowElement @@ -169,27 +175,8 @@ pagesElement.append(first.cloneNode(true)) } - function applyAutoplayIfNeeded(options) { - // prevent progress change if not infinite for first and last page - if ( - !infinite && ( - (autoplayDirection === NEXT && currentPageIndex === pagesCount - 1) || - (autoplayDirection === PREV && currentPageIndex === 0) - ) - ) { - progressManager.reset() - return - } - if (autoplay) { - const delayMs = get(options, 'delayMs', 0) - if (delayMs) { - setTimeout(() => { - autoplayDirectionFnDescription[autoplayDirection]() - }, delayMs) - } else { - autoplayDirectionFnDescription[autoplayDirection]() - } - } + async function applyAutoplay() { + await autoplayDirectionFnDescription[autoplayDirection]() } let cleanupFns = [] @@ -199,6 +186,16 @@ await tick() cleanupFns.push(store.subscribe(value => { currentPageIndex = value.currentPageIndex + + // prevent progress change if not infinite for first and last page + if ( + !infinite && ( + (autoplayDirection === NEXT && currentPageIndex === pagesCount - 1) || + (autoplayDirection === PREV && currentPageIndex === 0) + ) + ) { + progressManager.reset() + } })) cleanupFns.push(() => progressManager.reset()) if (pagesElement && pageWindowElement) { @@ -211,8 +208,6 @@ applyPageSizes() } - applyAutoplayIfNeeded() - addResizeEventListener(applyPageSizes) })() }) @@ -222,26 +217,30 @@ cleanupFns.filter(fn => fn && typeof fn === 'function').forEach(fn => fn()) }) - function handlePageChange(pageIndex) { - showPage(pageIndex + Number(infinite)) + async function handlePageChange(pageIndex) { + await showPage(pageIndex + Number(infinite)) } function offsetPage(animated) { - // _duration is an offset animation time - _duration = animated ? duration : 0 - offset = -currentPageIndex * pageWidth + return new Promise((resolve) => { + // _duration is an offset animation time + _duration = animated ? duration : 0 + offset = -currentPageIndex * pageWidth + setTimeout(() => { + resolve() + }, _duration) + }) } // makes delayed jump to 1st or last element - function jumpIfNeeded() { + async function jumpIfNeeded() { let jumped = false if (infinite) { if (currentPageIndex === 0) { - // offsetDelayMs should depend on _duration, as it wait when offset finishes - showPage(pagesCount - 2, { offsetDelayMs: _duration, animated: false }) + await showPage(pagesCount - 2, { animated: false }) jumped = true } else if (currentPageIndex === pagesCount - 1) { - showPage(1, { offsetDelayMs: _duration, animated: false }) + await showPage(1, { animated: false }) jumped = true } } @@ -250,54 +249,43 @@ // Disable page change while animation is in progress let disabled = false - function safeChangePage(cb, options) { - const animated = get(options, 'animated', true) + async function changePage(updateStoreFn, options) { if (disabled) return - cb() disabled = true - setTimeout(() => { - disabled = false - }, animated ? duration : 0) + + updateStoreFn() + await offsetPage(get(options, 'animated', true)) + disabled = false + + const jumped = await jumpIfNeeded() + !jumped && autoplay && applyAutoplay() // no need to wait it finishes } - function showPage(pageIndex, options) { - const animated = get(options, 'animated', true) - const offsetDelayMs = get(options, 'offsetDelayMs', 0) - safeChangePage(() => { - store.moveToPage({ pageIndex, pagesCount }) - // delayed page transition, used for infinite autoplay to jump to real page - setTimeout(() => { - offsetPage(animated) - const jumped = jumpIfNeeded() - !jumped && applyAutoplayIfNeeded({ delayMs: _duration }) // while offset animation is in progress (delayMs = _duration ms) wait for it - }, offsetDelayMs) - }, { animated }) + async function showPage(pageIndex, options) { + await changePage( + () => store.moveToPage({ pageIndex, pagesCount }), + options + ) } - function showPrevPage(options) { - const animated = get(options, 'animated', true) - safeChangePage(() => { - store.prev({ infinite, pagesCount }) - offsetPage(animated) - const jumped = jumpIfNeeded() - !jumped && applyAutoplayIfNeeded({ delayMs: _duration }) - }, { animated }) + async function showPrevPage(options) { + await changePage( + () => store.prev({ infinite, pagesCount }), + options + ) } - function showNextPage(options) { - const animated = get(options, 'animated', true) - safeChangePage(() => { - store.next({ infinite, pagesCount }) - offsetPage(animated) - const jumped = jumpIfNeeded() - !jumped && applyAutoplayIfNeeded({ delayMs: _duration }) - }, { animated }) + async function showNextPage(options) { + await changePage( + () => store.next({ infinite, pagesCount }), + options + ) } // gestures function handleSwipeStart() { _duration = 0 } - function handleThreshold(event) { - directionFnDescription[event.detail.direction]() + async function handleThreshold(event) { + await directionFnDescription[event.detail.direction]() } function handleSwipeMove(event) { offset += event.detail.dx diff --git a/src/utils/ProgressManager.js b/src/utils/ProgressManager.js index 457b333..e50ded0 100644 --- a/src/utils/ProgressManager.js +++ b/src/utils/ProgressManager.js @@ -17,25 +17,28 @@ export class ProgressManager { } start(onFinish) { - this.reset() + return new Promise((resolve) => { + this.reset() - const stepMs = Math.min(STEP_MS, this.#autoplayDuration) - let progress = -stepMs - - this.#interval = setIntervalImmediate(() => { - if (this.#paused) { - return - } - progress += stepMs - - const value = progress / this.#autoplayDuration - this.#onProgressValueChange(value) - - if (value > 1) { - this.reset() - onFinish() - } - }, stepMs) + const stepMs = Math.min(STEP_MS, this.#autoplayDuration) + let progress = -stepMs + + this.#interval = setIntervalImmediate(async () => { + if (this.#paused) { + return + } + progress += stepMs + + const value = progress / this.#autoplayDuration + this.#onProgressValueChange(value) + + if (value > 1) { + this.reset() + await onFinish() + resolve() + } + }, stepMs) + }) } pause() { diff --git a/src/utils/interval.js b/src/utils/interval.js index 49a9978..7ae9127 100644 --- a/src/utils/interval.js +++ b/src/utils/interval.js @@ -2,3 +2,11 @@ export const setIntervalImmediate = (fn, ms) => { fn(); return setInterval(fn, ms); } + +export const wait = (ms) => { + return new Promise((resolve) => { + setTimeout(() => { + resolve() + }, ms) + }) +} From b6532bc2c0aee908623f5ac72d04c12819df7c74 Mon Sep 17 00:00:00 2001 From: Vadim Date: Sun, 18 Jul 2021 13:10:01 +0300 Subject: [PATCH 2/6] #45 : Add comments --- src/components/Carousel/Carousel.svelte | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Carousel/Carousel.svelte b/src/components/Carousel/Carousel.svelte index c9279c7..3b2bf2d 100644 --- a/src/components/Carousel/Carousel.svelte +++ b/src/components/Carousel/Carousel.svelte @@ -112,7 +112,7 @@ let store = createStore() let currentPageIndex = 0 - $: originalCurrentPageIndex = getOriginalCurrentPageIndex(currentPageIndex, pagesCount, infinite) + $: originalCurrentPageIndex = getOriginalCurrentPageIndex(currentPageIndex, pagesCount, infinite) // index without cloenes $: dispatch('pageChange', originalCurrentPageIndex) let pagesCount = 0 @@ -120,8 +120,9 @@ function getOriginalCurrentPageIndex(currentPageIndex, pagesCount, infinite) { if (infinite) { + const CLONES_COUNT = 2 if (currentPageIndex === pagesCount - 1) return 0 - if (currentPageIndex === 0) return pagesCount - 3 + if (currentPageIndex === 0) return (pagesCount - CLONES_COUNT) - 1 return currentPageIndex - 1 } return currentPageIndex From 36d431e173a590a50ce841dbd6b40d3417cfdca4 Mon Sep 17 00:00:00 2001 From: Vadim Date: Sun, 18 Jul 2021 13:27:06 +0300 Subject: [PATCH 3/6] #45 : Update autoplay --- src/components/Carousel/Carousel.svelte | 23 +++++++++++++++-------- src/utils/ProgressManager.js | 1 + 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/components/Carousel/Carousel.svelte b/src/components/Carousel/Carousel.svelte index 3b2bf2d..7dd83ab 100644 --- a/src/components/Carousel/Carousel.svelte +++ b/src/components/Carousel/Carousel.svelte @@ -60,11 +60,7 @@ */ export let autoplay = false $: { - if (autoplay) { - applyAutoplay() - } else { - progressManager.reset() - } + applyAutoplayIfNeeded(autoplay) } /** @@ -176,8 +172,19 @@ pagesElement.append(first.cloneNode(true)) } - async function applyAutoplay() { - await autoplayDirectionFnDescription[autoplayDirection]() + async function applyAutoplayIfNeeded(autoplay) { + // prevent progress change if not infinite for first and last page + if ( + !infinite && ( + (autoplayDirection === NEXT && currentPageIndex === pagesCount - 1) || + (autoplayDirection === PREV && currentPageIndex === 0) + ) + ) { + progressManager.reset() + return + } + + autoplay && await autoplayDirectionFnDescription[autoplayDirection]() } let cleanupFns = [] @@ -259,7 +266,7 @@ disabled = false const jumped = await jumpIfNeeded() - !jumped && autoplay && applyAutoplay() // no need to wait it finishes + !jumped && applyAutoplayIfNeeded(autoplay) // no need to wait it finishes } async function showPage(pageIndex, options) { diff --git a/src/utils/ProgressManager.js b/src/utils/ProgressManager.js index e50ded0..2448648 100644 --- a/src/utils/ProgressManager.js +++ b/src/utils/ProgressManager.js @@ -51,5 +51,6 @@ export class ProgressManager { reset() { clearInterval(this.#interval) + this.#onProgressValueChange(1) } } From 08ad962dadf41e459c5a1993692e646d044b31a0 Mon Sep 17 00:00:00 2001 From: Vadim Date: Sun, 18 Jul 2021 13:30:29 +0300 Subject: [PATCH 4/6] #45 : Code cleanup --- src/components/Carousel/Carousel.svelte | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/components/Carousel/Carousel.svelte b/src/components/Carousel/Carousel.svelte index 7dd83ab..32b25dc 100644 --- a/src/components/Carousel/Carousel.svelte +++ b/src/components/Carousel/Carousel.svelte @@ -194,16 +194,6 @@ await tick() cleanupFns.push(store.subscribe(value => { currentPageIndex = value.currentPageIndex - - // prevent progress change if not infinite for first and last page - if ( - !infinite && ( - (autoplayDirection === NEXT && currentPageIndex === pagesCount - 1) || - (autoplayDirection === PREV && currentPageIndex === 0) - ) - ) { - progressManager.reset() - } })) cleanupFns.push(() => progressManager.reset()) if (pagesElement && pageWindowElement) { From 00e0a2a471b79d1ce357d572e92431d739da508d Mon Sep 17 00:00:00 2001 From: Vadim Date: Sun, 18 Jul 2021 16:18:45 +0300 Subject: [PATCH 5/6] #45 : Add unit tests --- src/utils/interval.test.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/utils/interval.test.js b/src/utils/interval.test.js index 9aa209f..2840443 100644 --- a/src/utils/interval.test.js +++ b/src/utils/interval.test.js @@ -1,5 +1,6 @@ import { setIntervalImmediate, + wait } from './interval.js' describe('setIntervalImmediate', () => { @@ -28,3 +29,18 @@ describe('setIntervalImmediate', () => { expect(clearInterval).toHaveBeenCalledWith(interval) }) }) + +describe('wait', () => { + beforeEach(() => { + jest.useFakeTimers(); + }) + + it('wait n ms', () => { + const ms = 1000 + + wait(ms) + jest.runAllTimers() + + expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), ms) + }) +}) From d9fdd87873d8bc5877fa3008a64714cdfd810a4b Mon Sep 17 00:00:00 2001 From: Vadim Date: Sun, 18 Jul 2021 16:21:38 +0300 Subject: [PATCH 6/6] #45 : Add const --- src/utils/ProgressManager.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/utils/ProgressManager.js b/src/utils/ProgressManager.js index 2448648..68b127e 100644 --- a/src/utils/ProgressManager.js +++ b/src/utils/ProgressManager.js @@ -1,6 +1,8 @@ import { setIntervalImmediate } from './interval' const STEP_MS = 35 +const MAX_VALUE = 1 + export class ProgressManager { #autoplayDuration #onProgressValueChange @@ -32,7 +34,7 @@ export class ProgressManager { const value = progress / this.#autoplayDuration this.#onProgressValueChange(value) - if (value > 1) { + if (value > MAX_VALUE) { this.reset() await onFinish() resolve() @@ -51,6 +53,6 @@ export class ProgressManager { reset() { clearInterval(this.#interval) - this.#onProgressValueChange(1) + this.#onProgressValueChange(MAX_VALUE) } }