import { OrderedMap } from "immutable"; import { BasicUpdater, Updater } from "../fun/domains/updater/state"; import { replaceWith } from "../fun/domains/updater/domains/replaceWith/state"; import { simpleUpdater } from "../fun/domains/updater/domains/simpleUpdater/state"; import { AsyncState } from "../async/state"; import { Unit } from "../fun/domains/unit/state"; import { BasicFun } from "../fun/state"; export type StreamingStatus = "reload" | "loadMore" | false; type Identifiable = { Id: string }; export type StreamPosition = { chunkSize: number; chunkIndex: number; shouldLoad: StreamingStatus; lastModifiedTime: number; }; export const StreamPosition = { Default: ( initialChunkSize: number, shouldLoad: StreamingStatus = false, ): StreamPosition => ({ chunkSize: initialChunkSize, chunkIndex: 0, shouldLoad, lastModifiedTime: Date.now(), }), Updaters: { Template: { loadInitial: (): Updater => StreamPosition.Updaters.Core.lastModifiedTime( replaceWith(Date.now()), ).then( StreamPosition.Updaters.Core.shouldLoad( replaceWith("loadMore"), ), ), changeChunkSize: (chunkSize: number): Updater => StreamPosition.Updaters.Core.chunkSize(replaceWith(chunkSize)).then( StreamPosition.Updaters.Template.reload(), ), reload: (): Updater => StreamPosition.Updaters.Core.lastModifiedTime(replaceWith(Date.now())) .then( StreamPosition.Updaters.Core.shouldLoad( replaceWith("reload"), ), ) .then( StreamPosition.Updaters.Core.chunkIndex( replaceWith(0), ), ), loadMore: (): Updater => StreamPosition.Updaters.Core.chunkIndex((_) => _ + 1).then( StreamPosition.Updaters.Core.lastModifiedTime( replaceWith(Date.now()), ).then( StreamPosition.Updaters.Core.shouldLoad( replaceWith("loadMore"), ), ), ), }, Core: { ...simpleUpdater()("lastModifiedTime"), ...simpleUpdater()("shouldLoad"), ...simpleUpdater()("chunkIndex"), ...simpleUpdater()("chunkSize"), }, Coroutine: {}, }, }; export type Chunk = { hasMoreValues: boolean; data: OrderedMap; }; export const Chunk = () => ({ Default: ( hasMoreValues: boolean, data: OrderedMap, ): Chunk => ({ hasMoreValues, data, }), }); export type InfiniteStreamState = { loadingMore: AsyncState; loadedElements: OrderedMap>; position: StreamPosition; getChunk: BasicFun<[StreamPosition], Promise>>; }; export const InfiniteStreamState = () => ({ Default: ( initialChunkSize: number, getChunk: InfiniteStreamState["getChunk"], shouldLoad?: StreamingStatus, ): InfiniteStreamState => ({ loadingMore: AsyncState.Default.unloaded(), loadedElements: OrderedMap(), position: StreamPosition.Default(initialChunkSize, shouldLoad), getChunk, }), Operations: { shouldCoroutineRun: (current: InfiniteStreamState): boolean => current.position.shouldLoad != false, loadNextPage: (current: InfiniteStreamState): boolean => current.position.shouldLoad !== false && current.loadedElements.last()?.hasMoreValues !== false, }, Updaters: { Coroutine: { addLoadedChunk: ( chunkIndex: number, newChunk: Chunk, ): Updater> => InfiniteStreamState().Updaters.Core.loadedElements((_) => _.set(chunkIndex, newChunk), ), }, Core: { ...simpleUpdater>()("getChunk"), ...simpleUpdater>()("loadingMore"), ...simpleUpdater>()("loadedElements"), whenNotAlreadyLoading: ( _: BasicUpdater>, ): Updater> => { return Updater((current) => { if (InfiniteStreamState().Operations.loadNextPage(current)) { return current; } return _(current); }); }, position: ( positionUpdater: Updater["position"]>, ): Updater> => Updater((current) => { const newPosition = positionUpdater(current.position); let newState = current; if (newPosition.chunkSize != current.position.chunkSize) newState = InfiniteStreamState().Updaters.Core.clearLoadedElements()( newState, ); return { ...newState, position: newPosition }; }), clearLoadedElements: (): Updater> => InfiniteStreamState().Updaters.Core.loadedElements((_) => OrderedMap(), ), }, Template: { loadInitial: (): Updater> => InfiniteStreamState().Updaters.Core.position( StreamPosition.Updaters.Template.loadInitial(), ), reload: ( getChunk: InfiniteStreamState["getChunk"], ): Updater> => InfiniteStreamState() .Updaters.Core.position(StreamPosition.Updaters.Template.reload()) .then( InfiniteStreamState().Updaters.Core.clearLoadedElements(), ) .then( InfiniteStreamState().Updaters.Core.getChunk( replaceWith(getChunk), ), ), loadMore: (): Updater> => InfiniteStreamState().Updaters.Core.whenNotAlreadyLoading( InfiniteStreamState().Updaters.Core.position( StreamPosition.Updaters.Template.loadMore(), ), ), }, }, }); export type InfiniteStreamReadonlyState = Unit; export type InfiniteStreamWritableState = InfiniteStreamState;