import { OrderedMap, Map } 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"; import { Guid, MapRepo, PredicateValue, Sum, ValueOrErrors, ValueRecord, } from "../../main"; export type ValueStreamingStatus = "reload" | "loadMore" | false; export type ValueStreamPosition = { nextStart: number; chunkSize: number; chunkIndex: ValueChunkIndex; shouldLoad: ValueStreamingStatus; lastModifiedTime: number; disallowParamsChange: boolean; }; export const ValueStreamPosition = { Default: ( initialChunkSize: number, shouldLoad: ValueStreamingStatus = false, ): ValueStreamPosition => ({ nextStart: 0, chunkSize: initialChunkSize, chunkIndex: 0, shouldLoad, lastModifiedTime: Date.now(), disallowParamsChange: false, }), Updaters: { Template: { changeChunkSize: (chunkSize: number): Updater => ValueStreamPosition.Updaters.Core.chunkSize( replaceWith(chunkSize), ).then(ValueStreamPosition.Updaters.Template.reload()), reload: (): Updater => ValueStreamPosition.Updaters.Core.lastModifiedTime( replaceWith(Date.now()), ) .then( ValueStreamPosition.Updaters.Core.shouldLoad( replaceWith("reload"), ), ) .then( ValueStreamPosition.Updaters.Core.chunkIndex( replaceWith(0), ), ), loadMore: (): Updater => ValueStreamPosition.Updaters.Core.chunkIndex((_) => _ + 1).then( ValueStreamPosition.Updaters.Core.lastModifiedTime( replaceWith(Date.now()), ).then( ValueStreamPosition.Updaters.Core.shouldLoad( replaceWith("loadMore"), ), ), ), initLoad: (): Updater => ValueStreamPosition.Updaters.Core.lastModifiedTime( replaceWith(Date.now()), ).then( ValueStreamPosition.Updaters.Core.shouldLoad( replaceWith("loadMore"), ), ), }, Core: { ...simpleUpdater()("disallowParamsChange"), ...simpleUpdater()("lastModifiedTime"), ...simpleUpdater()("shouldLoad"), ...simpleUpdater()("chunkIndex"), ...simpleUpdater()("chunkSize"), ...simpleUpdater()("nextStart"), }, Coroutine: {}, }, }; export type ValueChunk = { hasMoreValues: boolean; data: OrderedMap; from: number; to: number; }; export const ValueChunk = { Default: ( hasMoreValues: boolean, data: OrderedMap, from: number, to: number, ): ValueChunk => ({ hasMoreValues, data, from, to, }), Updaters: { Core: { ...simpleUpdater()("hasMoreValues"), ...simpleUpdater()("data"), ...simpleUpdater()("from"), ...simpleUpdater()("to"), }, Template: { updateValue: ( valueId: Guid, valueUpdater: BasicUpdater, ): Updater => ValueChunk.Updaters.Core.data( MapRepo.Updaters.update(valueId, valueUpdater), ), updateValueItem: ( valueId: Guid, itemId: Guid, valueUpdater: BasicUpdater, ): Updater => ValueChunk.Updaters.Core.data( MapRepo.Updaters.update( valueId, ValueRecord.Updaters.update(itemId, valueUpdater), ), ), }, }, }; export type ValueChunkIndex = number; export type StateChunkIndex = number; // TODO -- better state typing export type StateChunkValue = Map; export const StateChunkItem = { Default: (state: Record): StateChunkValue => Map(state), }; export type StateChunk = Map; export const StateChunk = { Default: (state: Record): StateChunk => Map(state), }; export type ValueInfiniteStreamState = { loadingMore: AsyncState; loadedElements: OrderedMap; position: ValueStreamPosition; getChunk: BasicFun<[ValueStreamPosition], Promise>; }; export const ValueInfiniteStreamState = { Default: ( initialChunkSize: number, getChunk: ValueInfiniteStreamState["getChunk"], shouldLoad?: ValueStreamingStatus, ): ValueInfiniteStreamState => ({ loadingMore: AsyncState.Default.unloaded(), loadedElements: OrderedMap(), position: ValueStreamPosition.Default(initialChunkSize, shouldLoad), getChunk, }), Operations: { shouldCoroutineRun: (current: ValueInfiniteStreamState): boolean => current.position.shouldLoad != false, loadNextPage: (current: ValueInfiniteStreamState): boolean => current.position.shouldLoad !== false && current.loadedElements.last()?.hasMoreValues !== false, getChunkIndexForValue: ( current: ValueInfiniteStreamState, valueId: Guid, ): ValueOrErrors => ValueOrErrors.Default.return( current.loadedElements.findLastKey((chunk) => chunk.data.has(valueId)), ).Then((chunkIndex) => chunkIndex == undefined ? ValueOrErrors.Default.throwOne( "value not found in any chunks", ) : ValueOrErrors.Default.return(chunkIndex), ), }, Updaters: { Coroutine: { addLoadedChunk: ( chunkIndex: number, newChunk: ValueChunk, ): Updater => ValueInfiniteStreamState.Updaters.Core.loadedElements((_) => _.set(chunkIndex, newChunk), ), }, Core: { ...simpleUpdater()("getChunk"), ...simpleUpdater()("loadingMore"), ...simpleUpdater()("loadedElements"), whenNotAlreadyLoading: ( _: BasicUpdater, ): Updater => { return Updater((current) => { if (ValueInfiniteStreamState.Operations.loadNextPage(current)) { return current; } return _(current); }); }, position: ( positionUpdater: Updater, ): Updater => Updater((current) => { const newPosition = positionUpdater(current.position); let newState = current; if (newPosition.chunkSize != current.position.chunkSize) newState = ValueInfiniteStreamState.Updaters.Core.clearLoadedElements()( newState, ); return { ...newState, position: newPosition }; }), clearLoadedElements: (): Updater => ValueInfiniteStreamState.Updaters.Core.loadedElements((_) => OrderedMap(), ), }, Template: { reload: ( getChunk: ValueInfiniteStreamState["getChunk"], ): Updater => ValueInfiniteStreamState.Updaters.Core.position( ValueStreamPosition.Updaters.Template.reload(), ) .then(ValueInfiniteStreamState.Updaters.Core.clearLoadedElements()) .then( ValueInfiniteStreamState.Updaters.Core.getChunk( replaceWith(getChunk), ), ), loadMore: (): Updater => ValueInfiniteStreamState.Updaters.Core.whenNotAlreadyLoading( ValueInfiniteStreamState.Updaters.Core.position( ValueStreamPosition.Updaters.Template.loadMore(), ), ), initLoad: (): Updater => ValueInfiniteStreamState.Updaters.Core.whenNotAlreadyLoading( ValueInfiniteStreamState.Updaters.Core.position( ValueStreamPosition.Updaters.Template.initLoad(), ), ), updateChunkValue: (chunkIndex: number, chunkValueKey: Guid) => ( valueUpdater: BasicUpdater, ): Updater => ValueInfiniteStreamState.Updaters.Core.loadedElements( MapRepo.Updaters.update( chunkIndex, ValueChunk.Updaters.Template.updateValue( chunkValueKey, valueUpdater, ), ), ), updateChunkValueItem: (chunkIndex: number, chunkValueKey: Guid, chunkValueItemKey: Guid) => ( valueUpdater: BasicUpdater, ): Updater => ValueInfiniteStreamState.Updaters.Core.loadedElements( MapRepo.Updaters.update( chunkIndex, ValueChunk.Updaters.Template.updateValueItem( chunkValueKey, chunkValueItemKey, valueUpdater, ), ), ), }, }, }; export type ValueInfiniteStreamReadonlyContext = Unit; export type ValueInfiniteStreamWritableState = ValueInfiniteStreamState;