Merge pull request #46 from vadimkorr/feature/45-promisify-functions

feature/45 Promisify functions
This commit is contained in:
Vadim
2021-07-18 16:22:10 +03:00
committed by GitHub
4 changed files with 116 additions and 100 deletions

View File

@@ -13,30 +13,19 @@
} from '../../utils/event' } from '../../utils/event'
import { getAdjacentIndexes } from '../../utils/page' import { getAdjacentIndexes } from '../../utils/page'
import { get } from '../../utils/object' 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 dispatch = createEventDispatcher()
const autoplayDirectionFnDescription = { const autoplayDirectionFnDescription = {
[NEXT]: () => { [NEXT]: async () => await progressManager.start(showNextPage),
progressManager.start(() => { [PREV]: async () => await progressManager.start(showPrevPage)
showNextPage()
})
},
[PREV]: () => {
progressManager.start(() => {
showPrevPage()
})
}
} }
const directionFnDescription = { const directionFnDescription = {
[NEXT]: () => { [NEXT]: showNextPage,
showNextPage() [PREV]: showPrevPage
},
[PREV]: () => {
showPrevPage()
}
} }
/** /**
@@ -70,6 +59,9 @@
* Enables autoplay of pages * Enables autoplay of pages
*/ */
export let autoplay = false export let autoplay = false
$: {
applyAutoplayIfNeeded(autoplay)
}
/** /**
* Autoplay change interval (ms) * Autoplay change interval (ms)
@@ -96,31 +88,42 @@
*/ */
export let dots = true export let dots = true
export function goTo(pageIndex, options) { export async function goTo(pageIndex, options) {
const animated = get(options, 'animated', true) const animated = get(options, 'animated', true)
if (typeof pageIndex !== 'number') { if (typeof pageIndex !== 'number') {
throw new Error('pageIndex should be a 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) 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) const animated = get(options, 'animated', true)
showNextPage({ animated }) await showNextPage({ animated })
} }
let store = createStore() let store = createStore()
let currentPageIndex = 0 let currentPageIndex = 0
$: originalCurrentPageIndex = currentPageIndex - Number(infinite); $: originalCurrentPageIndex = getOriginalCurrentPageIndex(currentPageIndex, pagesCount, infinite) // index without cloenes
$: dispatch('pageChange', originalCurrentPageIndex) $: dispatch('pageChange', originalCurrentPageIndex)
let pagesCount = 0 let pagesCount = 0
$: originalPagesCount = Math.max(pagesCount - (infinite ? 2 : 0), 1) // without clones $: originalPagesCount = Math.max(pagesCount - (infinite ? 2 : 0), 1) // without clones
function getOriginalCurrentPageIndex(currentPageIndex, pagesCount, infinite) {
if (infinite) {
const CLONES_COUNT = 2
if (currentPageIndex === pagesCount - 1) return 0
if (currentPageIndex === 0) return (pagesCount - CLONES_COUNT) - 1
return currentPageIndex - 1
}
return currentPageIndex
}
let pageWidth = 0 let pageWidth = 0
let offset = 0 let offset = 0
let pageWindowElement let pageWindowElement
@@ -169,7 +172,7 @@
pagesElement.append(first.cloneNode(true)) pagesElement.append(first.cloneNode(true))
} }
function applyAutoplayIfNeeded(options) { async function applyAutoplayIfNeeded(autoplay) {
// prevent progress change if not infinite for first and last page // prevent progress change if not infinite for first and last page
if ( if (
!infinite && ( !infinite && (
@@ -180,16 +183,8 @@
progressManager.reset() progressManager.reset()
return return
} }
if (autoplay) {
const delayMs = get(options, 'delayMs', 0) autoplay && await autoplayDirectionFnDescription[autoplayDirection]()
if (delayMs) {
setTimeout(() => {
autoplayDirectionFnDescription[autoplayDirection]()
}, delayMs)
} else {
autoplayDirectionFnDescription[autoplayDirection]()
}
}
} }
let cleanupFns = [] let cleanupFns = []
@@ -211,8 +206,6 @@
applyPageSizes() applyPageSizes()
} }
applyAutoplayIfNeeded()
addResizeEventListener(applyPageSizes) addResizeEventListener(applyPageSizes)
})() })()
}) })
@@ -222,26 +215,30 @@
cleanupFns.filter(fn => fn && typeof fn === 'function').forEach(fn => fn()) cleanupFns.filter(fn => fn && typeof fn === 'function').forEach(fn => fn())
}) })
function handlePageChange(pageIndex) { async function handlePageChange(pageIndex) {
showPage(pageIndex + Number(infinite)) await showPage(pageIndex + Number(infinite))
} }
function offsetPage(animated) { function offsetPage(animated) {
// _duration is an offset animation time return new Promise((resolve) => {
_duration = animated ? duration : 0 // _duration is an offset animation time
offset = -currentPageIndex * pageWidth _duration = animated ? duration : 0
offset = -currentPageIndex * pageWidth
setTimeout(() => {
resolve()
}, _duration)
})
} }
// makes delayed jump to 1st or last element // makes delayed jump to 1st or last element
function jumpIfNeeded() { async function jumpIfNeeded() {
let jumped = false let jumped = false
if (infinite) { if (infinite) {
if (currentPageIndex === 0) { if (currentPageIndex === 0) {
// offsetDelayMs should depend on _duration, as it wait when offset finishes await showPage(pagesCount - 2, { animated: false })
showPage(pagesCount - 2, { offsetDelayMs: _duration, animated: false })
jumped = true jumped = true
} else if (currentPageIndex === pagesCount - 1) { } else if (currentPageIndex === pagesCount - 1) {
showPage(1, { offsetDelayMs: _duration, animated: false }) await showPage(1, { animated: false })
jumped = true jumped = true
} }
} }
@@ -250,54 +247,43 @@
// Disable page change while animation is in progress // Disable page change while animation is in progress
let disabled = false let disabled = false
function safeChangePage(cb, options) { async function changePage(updateStoreFn, options) {
const animated = get(options, 'animated', true)
if (disabled) return if (disabled) return
cb()
disabled = true disabled = true
setTimeout(() => {
disabled = false updateStoreFn()
}, animated ? duration : 0) await offsetPage(get(options, 'animated', true))
disabled = false
const jumped = await jumpIfNeeded()
!jumped && applyAutoplayIfNeeded(autoplay) // no need to wait it finishes
} }
function showPage(pageIndex, options) { async function showPage(pageIndex, options) {
const animated = get(options, 'animated', true) await changePage(
const offsetDelayMs = get(options, 'offsetDelayMs', 0) () => store.moveToPage({ pageIndex, pagesCount }),
safeChangePage(() => { options
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 })
} }
function showPrevPage(options) { async function showPrevPage(options) {
const animated = get(options, 'animated', true) await changePage(
safeChangePage(() => { () => store.prev({ infinite, pagesCount }),
store.prev({ infinite, pagesCount }) options
offsetPage(animated) )
const jumped = jumpIfNeeded()
!jumped && applyAutoplayIfNeeded({ delayMs: _duration })
}, { animated })
} }
function showNextPage(options) { async function showNextPage(options) {
const animated = get(options, 'animated', true) await changePage(
safeChangePage(() => { () => store.next({ infinite, pagesCount }),
store.next({ infinite, pagesCount }) options
offsetPage(animated) )
const jumped = jumpIfNeeded()
!jumped && applyAutoplayIfNeeded({ delayMs: _duration })
}, { animated })
} }
// gestures // gestures
function handleSwipeStart() { function handleSwipeStart() {
_duration = 0 _duration = 0
} }
function handleThreshold(event) { async function handleThreshold(event) {
directionFnDescription[event.detail.direction]() await directionFnDescription[event.detail.direction]()
} }
function handleSwipeMove(event) { function handleSwipeMove(event) {
offset += event.detail.dx offset += event.detail.dx

View File

@@ -1,6 +1,8 @@
import { setIntervalImmediate } from './interval' import { setIntervalImmediate } from './interval'
const STEP_MS = 35 const STEP_MS = 35
const MAX_VALUE = 1
export class ProgressManager { export class ProgressManager {
#autoplayDuration #autoplayDuration
#onProgressValueChange #onProgressValueChange
@@ -17,25 +19,28 @@ export class ProgressManager {
} }
start(onFinish) { start(onFinish) {
this.reset() return new Promise((resolve) => {
this.reset()
const stepMs = Math.min(STEP_MS, this.#autoplayDuration) const stepMs = Math.min(STEP_MS, this.#autoplayDuration)
let progress = -stepMs let progress = -stepMs
this.#interval = setIntervalImmediate(() => { this.#interval = setIntervalImmediate(async () => {
if (this.#paused) { if (this.#paused) {
return return
} }
progress += stepMs progress += stepMs
const value = progress / this.#autoplayDuration const value = progress / this.#autoplayDuration
this.#onProgressValueChange(value) this.#onProgressValueChange(value)
if (value > 1) { if (value > MAX_VALUE) {
this.reset() this.reset()
onFinish() await onFinish()
} resolve()
}, stepMs) }
}, stepMs)
})
} }
pause() { pause() {
@@ -48,5 +53,6 @@ export class ProgressManager {
reset() { reset() {
clearInterval(this.#interval) clearInterval(this.#interval)
this.#onProgressValueChange(MAX_VALUE)
} }
} }

View File

@@ -2,3 +2,11 @@ export const setIntervalImmediate = (fn, ms) => {
fn(); fn();
return setInterval(fn, ms); return setInterval(fn, ms);
} }
export const wait = (ms) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, ms)
})
}

View File

@@ -1,5 +1,6 @@
import { import {
setIntervalImmediate, setIntervalImmediate,
wait
} from './interval.js' } from './interval.js'
describe('setIntervalImmediate', () => { describe('setIntervalImmediate', () => {
@@ -28,3 +29,18 @@ describe('setIntervalImmediate', () => {
expect(clearInterval).toHaveBeenCalledWith(interval) 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)
})
})