From 04fc80ecec97881b3f316df2eb866c53ec551bf8 Mon Sep 17 00:00:00 2001 From: Vadim Date: Tue, 5 Oct 2021 19:33:07 +0300 Subject: [PATCH] Add reactivity --- src/components/Carousel/Carousel.svelte | 139 ++++++++--------------- src/components/Carousel/carousel2.js | 111 ++++++++++++++++++ src/components/Carousel/reactive.js | 142 +++++++++++++++--------- 3 files changed, 249 insertions(+), 143 deletions(-) create mode 100644 src/components/Carousel/carousel2.js diff --git a/src/components/Carousel/Carousel.svelte b/src/components/Carousel/Carousel.svelte index e32a4c0..e6dc87c 100644 --- a/src/components/Carousel/Carousel.svelte +++ b/src/components/Carousel/Carousel.svelte @@ -31,22 +31,18 @@ import { wait } from '../../utils/interval' import { carouselEngine } from './carousel' + import { carousel2 } from './carousel2' import { reactive } from './reactive' - const data = reactive( - { - message: 'hello', - someValue: 'some-value' - }, - { - renderFunction: (data) => { - console.log('renderFunction watcher called', data.message) - }, - } - ) - console.log('console', data) + let currentPageIndex - data.message = 'Hello!' + const [data, methods] = carousel2((key, value) => { + // console.log('onChange', key, value) + if (key === 'currentPageIndex') { + currentPageIndex = value + } + }) // put init data + const dispatch = createEventDispatcher() @@ -76,7 +72,7 @@ */ export let infinite $: { - setInfinite(infinite) + data.infinite = infinite } /** @@ -151,13 +147,13 @@ throw new Error('pageIndex should be a number') } await showParticle(getParticleIndexByPageIndex({ - infinite, + infinite: data.infinite, pageIndex, - clonesCountHead: clonesCount.head, - clonesCountTail: clonesCount.tail, - particlesToScroll, - particlesCount, - particlesToShow, + clonesCountHead: data.clonesCountHead, + clonesCountTail: data.clonesCountTail, + particlesToScroll: data.particlesToScroll, + particlesCount: data.particlesCount, + particlesToShow: data.particlesToShow, }), { animated }) } @@ -171,44 +167,6 @@ await showNextPage({ animated }) } - - ///// ========================== - let currentPageIndex - let pagesCount - let particlesCountWithoutClones - let clonesCount - let currentParticleIndex = 0 - let particlesCount = 0 - let _particlesToShow - let _particlesToScroll - - const { - setParticlesToShow, - setParticlesToScroll, - setParticlesCountWithoutClones, - setPartialPageSize, - setParticlesCount, - setCurrentParticleIndex, - setInitialPageIndex, - setInfinite, - prev, - next, - moveToParticle, - } = carouselEngine((values, computed) => { - // console.log('hello') - currentPageIndex = computed.currentPageIndex - pagesCount = computed.pagesCount - particlesCountWithoutClones = values.particlesCountWithoutClones - clonesCount = computed.clonesCount - currentParticleIndex = values.currentParticleIndex - particlesCount = values.particlesCount - _particlesToShow = values.particlesToShow - _particlesToScroll = values.particlesToScroll - }) - - ///// ========================== - - let pageWindowWidth = 0 let particleWidth = 0 let offset = 0 @@ -220,7 +178,7 @@ width, }) => { pageWindowWidth = width - particleWidth = pageWindowWidth / _particlesToShow + particleWidth = pageWindowWidth / data.particlesToShow applyParticleSizes({ particlesContainerChildren: particlesContainer.children, @@ -267,8 +225,8 @@ clonesToAppend, clonesToPrepend, } = getClones({ - clonesCountHead: clonesCount.head, - clonesCountTail: clonesCount.tail, + clonesCountHead: data.clonesCountHead, + clonesCountTail: data.clonesCountTail, particlesContainerChildren: particlesContainer.children, }) applyClones({ @@ -281,9 +239,9 @@ async function applyAutoplayIfNeeded(autoplay) { // prevent progress change if not infinite for first and last page if ( - !infinite && ( - (autoplayDirection === NEXT && currentParticleIndex === particlesCount - 1) || - (autoplayDirection === PREV && currentParticleIndex === 0) + !data.infinite && ( + (autoplayDirection === NEXT && data.currentParticleIndex === data.particlesCount - 1) || + (autoplayDirection === PREV && data.currentParticleIndex === 0) ) ) { progressManager.reset() @@ -302,16 +260,16 @@ await tick() cleanupFns.push(() => progressManager.reset()) if (particlesContainer && pageWindowElement) { - setParticlesCountWithoutClones(particlesContainer.children.length) - setParticlesToShow(particlesToShow) - setParticlesToScroll(particlesToScroll) + data.particlesCountWithoutClones = particlesContainer.children.length + data.particlesToShow = particlesToShow + data.particlesToScroll = particlesToScroll await tick() - infinite && addClones() + data.infinite && addClones() // call after adding clones - setParticlesCount(particlesContainer.children.length) - setInitialPageIndex(initialPageIndex) + data.particlesCount = particlesContainer.children.length + data.initialPageIndex = initialPageIndex pageWindowElementResizeObserver.observe(pageWindowElement); } @@ -325,13 +283,13 @@ async function handlePageChange(pageIndex) { await showParticle(getParticleIndexByPageIndex({ - infinite, + infinite: data.infinite, pageIndex, - clonesCountHead: clonesCount.head, - clonesCountTail: clonesCount.tail, - particlesToScroll, - particlesCount, - particlesToShow: _particlesToShow, + clonesCountHead: data.clonesCountHead, + clonesCountTail: data.clonesCountTail, + particlesToScroll: data.particlesToScroll, + particlesCount: data.particlesCount, + particlesToShow: data.particlesToShow, })) } @@ -340,7 +298,7 @@ return new Promise((resolve) => { // _duration is an offset animation time _duration = animated ? duration : 0 - offset = -currentParticleIndex * particleWidth + offset = -data.currentParticleIndex * particleWidth setTimeout(() => { resolve() }, _duration) @@ -350,12 +308,12 @@ // makes delayed jump to 1st or last element async function jumpIfNeeded() { let jumped = false - if (infinite) { - if (currentParticleIndex === 0) { - await showParticle(particlesCount - clonesCount.total, { animated: false }) + if (data.infinite) { + if (data.currentParticleIndex === 0) { + await showParticle(data.particlesCount - data.clonesCountTotal, { animated: false }) jumped = true - } else if (currentParticleIndex === particlesCount - clonesCount.tail) { - await showParticle(clonesCount.head, { animated: false }) + } else if (data.currentParticleIndex === data.particlesCount - data.clonesCountTail) { + await showParticle(data.clonesCountHead, { animated: false }) jumped = true } } @@ -378,8 +336,9 @@ } async function showParticle(particleIndex, options) { + console.log('showParticle => particleIndex', particleIndex) await changePage( - () => moveToParticle(particleIndex), + () => methods.moveToParticle(particleIndex), options ) } @@ -387,7 +346,7 @@ if (disabled) return await changePage( - prev, + methods.prev, options, ) } @@ -395,7 +354,7 @@ if (disabled) return await changePage( - next, + methods.next, options, ) } @@ -415,7 +374,7 @@ } function handleSwipeEnd() { if (!swiping) return - showParticle(currentParticleIndex) + showParticle(data.currentParticleIndex) } async function handleSwipeFailed() { if (!swiping) return @@ -437,7 +396,7 @@ @@ -481,7 +440,7 @@ @@ -492,11 +451,11 @@ handlePageChange(event.detail)} > diff --git a/src/components/Carousel/carousel2.js b/src/components/Carousel/carousel2.js new file mode 100644 index 0000000..9134a1c --- /dev/null +++ b/src/components/Carousel/carousel2.js @@ -0,0 +1,111 @@ +import { NEXT, PREV } from '../../direction' +import { + applyParticleSizes, + getCurrentPageIndexByCurrentParticleIndex, + getPartialPageSize, + getPagesCountByParticlesCount, + getParticleIndexByPageIndex, + createResizeObserver, +} from '../../utils/page' +import { getClones, applyClones, getClonesCount } from '../../utils/clones' +import { getAdjacentIndexes } from '../../utils/lazy' +import { getValueInRange } from '../../utils/math' +import { get } from '../../utils/object' +import { ProgressManager } from '../../utils/ProgressManager' +import { wait } from '../../utils/interval' +import { reactive } from './reactive' + +// return only getters +export const carousel2 = (onChange) => { + return reactive( + { + particlesCountWithoutClones: 0, + particlesToShow: 1, + particlesToScroll: 1, + initialPageIndex: 1, + particlesCount: 1, + currentParticleIndex: 1, + infinite: false, + clonesCountHead: 0, + clonesCountTail: 0, + clonesCountTotal: 0, + partialPageSize: 1, + currentPageIndex: 1, + pagesCount: 1, + }, + { + setCurrentPageIndex: (data) => { + const ind = getCurrentPageIndexByCurrentParticleIndex({ + currentParticleIndex: data.currentParticleIndex, + particlesCount: data.particlesCount, + clonesCountHead: data.clonesCountHead, + clonesCountTotal: data.clonesCountTotal, + infinite: data.initialPageIndex, + particlesToScroll: data.particlesToScroll, + }) + data.currentPageIndex = ind + console.log('===> data.currentPageIndex', ind) + }, + setPartialPageSize: (data) => { + data.partialPageSize = getPartialPageSize({ + particlesToScroll: data.particlesToScroll, + particlesToShow: data.particlesToShow, + particlesCountWithoutClones: data.particlesCountWithoutClones, + }) + }, + setClonesCount: (data) => { + const { head, tail } = getClonesCount({ + infinite: data.infinite, + particlesToShow: data.particlesToShow, + partialPageSize: data.partialPageSize, + }) + + data.clonesCountHead = head + data.clonesCountTail = tail + data.clonesCountTotal = head + tail + }, + setPagesCount: (data) => { + data.pagesCount = getPagesCountByParticlesCount({ + infinite: data.infinite, + particlesCountWithoutClones: data.particlesCountWithoutClones, + particlesToScroll: data.particlesToScroll, + }) + }, + }, + { + prev: (data) => { + const newCurrentParticleIndex = getParticleIndexByPageIndex({ + infinite: data.infinite, + pageIndex: data.currentPageIndex - 1, + clonesCountHead: data.clonesCountHead, + clonesCountTail: data.clonesCountTail, + particlesToScroll: data.particlesToScroll, + particlesCount: data.particlesCount, + particlesToShow: data.particlesToShow, + }) + data.currentParticleIndex = newCurrentParticleIndex + }, + next: (data) => { + const newCurrentParticleIndex = getParticleIndexByPageIndex({ + infinite: data.infinite, + pageIndex: data.currentPageIndex + 1, + clonesCountHead: data.clonesCountHead, + clonesCountTail: data.clonesCountTail, + particlesToScroll: data.particlesToScroll, + particlesCount: data.particlesCount, + particlesToShow: data.particlesToShow, + }) + data.currentParticleIndex = newCurrentParticleIndex + }, + moveToParticle: (data, particleIndex) => { + const newCurrentParticleIndex = getValueInRange( + 0, + particleIndex, + data.particlesCount - 1 + ) + data.currentParticleIndex = newCurrentParticleIndex + }, + }, + onChange + ) +} diff --git a/src/components/Carousel/reactive.js b/src/components/Carousel/reactive.js index cb0518b..cfab51f 100644 --- a/src/components/Carousel/reactive.js +++ b/src/components/Carousel/reactive.js @@ -1,80 +1,116 @@ -// let data = { -// message: '', -// } - -// let methods = { -// changeMessage: function () { -// data.message = document.getElementById('messageInput').value -// }, -// } - // Code that has to run when a // reactive property changes it's value. -let target = null -class Dep { - constructor() { - this.subscribers = [] - } - depend() { - // Saves target function into subscribers array - if (target && !this.subscribers.includes(target)) { - this.subscribers.push(target) +const objectsAreSame = (x, y) => { + // return false + let _objectsAreSame = true + for (let propertyName in x) { + if (Number.isNaN(x[propertyName]) || Number.isNaN(y[propertyName])) { + continue + } + if (x[propertyName] !== y[propertyName]) { + _objectsAreSame = false + break } } - notify() { - // Replays target functions saved in the subscribers array - this.subscribers.forEach((sub) => sub()) + + return _objectsAreSame +} + +const getObject = (oldData, newData) => { + const newDeps = {} + + Object.entries(oldData).forEach(([key, value]) => { + // console.log('oldData', key, value) + newDeps[key] = newData[key] + }) + // console.log('isDiff', oldData, newDeps) + + return newDeps +} + +const useSubscription = () => { + const subscribers = {} + + const memoDependency = (target, dep) => { + const { watcherName, fn } = target + const { key, value } = dep + + if (!subscribers[watcherName]) { + subscribers[watcherName] = { + deps: {}, + fn, + } + } + subscribers[watcherName].deps[key] = value + } + + return { + subscribe: (target, dep) => { + if (target) { + memoDependency(target, dep) + } + }, + notify: (data) => { + Object.entries(subscribers).forEach(([watcherName, { deps }]) => { + const newDeps = getObject(deps, data) + if (!objectsAreSame(deps, newDeps)) { + subscribers[watcherName].deps = newDeps + subscribers[watcherName].fn() + } + }) + }, } } -let watch = function (func) { - // Here, a watcher is a function that encapsulates the code - // that needs to recorded/watched. - // PS: It just runs once, because after that, the target code is stored - // in the subscriber's list of the Dep() instance. - target = func // Then it assigns the function to target - target() // Run the target function - target = null // Reset target to null +const useWatcher = () => { + let target = null + + return { + watch: (watcherName, fn) => { + target = { + watcherName, + fn, + } + target.fn() + target = null + }, + getTarget: () => { + return target + }, + } } -// reactive( -// { -// message: '', -// }, -// { -// renderFunction: (data) => { -// console.log(data.message) -// }, -// } -// ) +export const reactive = (data, watchers, methods, onChange) => { + const { subscribe, notify } = useSubscription() + const { watch, getTarget } = useWatcher() -export const reactive = (data, watchers, methods) => { const _data = {} Object.keys(data).forEach((key) => { - let internalValue = data[key] - - // Each property gets a dependency instance - const dep = new Dep() + let currentValue = data[key] Object.defineProperty(_data, key, { get() { - console.log(`Getting value, ${internalValue}`) - dep.depend() // Saves the target function into the subscribers array - return internalValue + subscribe(getTarget(), { key, value: currentValue }) + return currentValue }, set(newVal) { - console.log(`Setting the internalValue to ${newVal}`) - internalValue = newVal - dep.notify() // Reruns saved target functions in the subscribers array + currentValue = newVal + onChange && onChange(key, newVal) + notify(_data) }, }) }) Object.entries(watchers).forEach(([watcherName, watcher]) => { - watch(() => watcher(_data)) + watch(watcherName, () => watcher(_data)) }) - return _data + const _methods = {} + Object.entries(methods).forEach(([methodName, method]) => { + _methods[methodName] = (args) => method(_data, args) + }) + + return [_data, _methods] }