import { AbstractCrdt , Doc , ApplyResult } from "./AbstractCrdt" ; import { deserialize , selfOrRegister , selfOrRegisterValue } from "./utils" ; import { SerializedList , SerializedCrdtWithId , Op , CreateListOp , OpType , } from "./live" ; import { makePosition , compare } from "./position" ; type LiveListItem = [ crdt : AbstractCrdt , position : string ] ; /**
 * The LiveList class represents an ordered collection of items that is synchorinized across clients.
 */ export class LiveList < T > extends AbstractCrdt { #items : Array < LiveListItem > = [ ] ; constructor ( items = [ ] ) { super ( ) ; let position = undefined ; for ( let i = 0 ; i < items . length ; i ++ ) { const newPosition = makePosition ( position ) ; const item = selfOrRegister ( items [ i ] ) ; this . #items . push ( [ item , newPosition ] ) ; position = newPosition ; } } /**
     * INTERNAL
     */ static _deserialize ( [ id , item ] , parentToChildren , doc ) { const list = new LiveList ( [ ] ) ; list . _attach ( id , doc ) ; const children = parentToChildren . get ( id ) ; if ( children == null ) { return list ; } for ( const entry of children ) { const child = deserialize ( entry , parentToChildren , doc ) ; child . _setParentLink ( list , entry [ 1 ] . parentKey ! ) ; list . #items . push ( [ child , entry [ 1 ] . parentKey ! ] ) ; list . #items . sort ( ( itemA , itemB ) => compare ( itemA [ 1 ] , itemB [ 1 ] ) ) ; } return list ; } /**
     * INTERNAL
     */ _serialize ( parentId ? , parentKey ? ) { if ( this . _id == null ) { throw new Error ( "Cannot serialize item is not attached" ) ; } if ( parentId == null || parentKey == null ) { throw new Error ( "Cannot serialize list if parentId or parentKey is undefined" ) ; } const ops = [ ] ; const op = { id : this . _id , type : OpType . CreateList , parentId , parentKey , } ; ops . push ( op ) ; for ( const [ value , key ] of this . #items ) { ops . push ( ... value . _serialize ( this . _id , key ) ) ; } return ops ; } /**
     * INTERNAL
     */ _attach ( id , doc ) { super . _attach ( id , doc ) ; for ( const [ item , position ] of this . #items ) { item . _attach ( doc . generateId ( ) , doc ) ; } } /**
     * INTERNAL
     */ _detach ( ) { super . _detach ( ) ; for ( const [ value ] of this . #items ) { value . _detach ( ) ; } } /**
     * INTERNAL
     */ _attachChild ( id , key , child ) { if ( this . _doc == null ) { throw new Error ( "Can't attach child if doc is not present" ) ; } child . _attach ( id , this . _doc ) ; child . _setParentLink ( this , key ) ; const index = this . #items . findIndex ( ( entry ) => entry [ 1 ] === key ) ; if ( index !== - 1 ) { this . #items [ index ] [ 1 ] = makePosition ( key , this . #items [ index + 1 ] ?. [ 1 ] ) ; } this . #items . push ( [ child , key ] ) ; this . #items . sort ( ( itemA , itemB ) => compare ( itemA [ 1 ] , itemB [ 1 ] ) ) ; return { reverse : [ { type : OpType . DeleteCrdt , id } ] , modified : this } ; } /**
     * INTERNAL
     */ _detachChild ( child ) { const indexToDelete = this . #items . findIndex ( ( item ) => item [ 0 ] === child ) ; this . #items . splice ( indexToDelete , 1 ) ; if ( child ) { child . _detach ( ) ; } } /**
     * INTERNAL
     */ _setChildKey ( key , child ) { child . _setParentLink ( this , key ) ; const index = this . #items . findIndex ( ( entry ) => entry [ 1 ] === key ) ; if ( index !== - 1 ) { this . #items [ index ] [ 1 ] = makePosition ( key , this . #items [ index + 1 ] ?. [ 1 ] ) ; } const item = this . #items . find ( ( item ) => item [ 0 ] === child ) ; if ( item ) { item [ 1 ] = key ; } this . #items . sort ( ( itemA , itemB ) => compare ( itemA [ 1 ] , itemB [ 1 ] ) ) ; } /**
     * INTERNAL
     */ _apply ( op ) { return super . _apply ( op ) ; } /**
     * Returns the number of elements.
     */ get length ( ) { return this . #items . length ; } param element push ( element ) { return this . insert ( element , this . length ) ; } param element param index insert ( element , index ) { if ( index < 0 || index > this . #items . length ) { throw new Error ( `Cannot delete list item at index "${ index }". index should be between 0 and ${ this . #items . length }` ) ; } let before = this . #items [ index - 1 ] ? this . #items [ index - 1 ] [ 1 ] : undefined ; let after = this . #items [ index ] ? this . #items [ index ] [ 1 ] : undefined ; const position = makePosition ( before , after ) ; const value = selfOrRegister ( element ) ; value . _setParentLink ( this , position ) ; this . #items . push ( [ value , position ] ) ; this . #items . sort ( ( itemA , itemB ) => compare ( itemA [ 1 ] , itemB [ 1 ] ) ) ; if ( this . _doc && this . _id ) { const id = this . _doc . generateId ( ) ; value . _attach ( id , this . _doc ) ; this . _doc . dispatch ( value . _serialize ( this . _id , position ) , [ { type : OpType . DeleteCrdt , id } ] , [ this ] ) ; } } param index param targetIndex move ( index , targetIndex ) { if ( targetIndex < 0 ) { throw new Error ( "targetIndex cannot be less than 0" ) ; } if ( targetIndex >= this . #items . length ) { throw new Error ( "targetIndex cannot be greater or equal than the list length" ) ; } if ( index < 0 ) { throw new Error ( "index cannot be less than 0" ) ; } if ( index >= this . #items . length ) { throw new Error ( "index cannot be greater or equal than the list length" ) ; } let beforePosition = null ; let afterPosition = null ; if ( index < targetIndex ) { afterPosition = targetIndex === this . #items . length - 1 ? undefined : this . #items [ targetIndex + 1 ] [ 1 ] ; beforePosition = this . #items [ targetIndex ] [ 1 ] ; } else { afterPosition = this . #items [ targetIndex ] [ 1 ] ; beforePosition = targetIndex === 0 ? undefined : this . #items [ targetIndex - 1 ] [ 1 ] ; } const position = makePosition ( beforePosition , afterPosition ) ; const item = this . #items [ index ] ; const previousPosition = item [ 1 ] ; item [ 1 ] = position ; item [ 0 ] . _setParentLink ( this , position ) ; this . #items . sort ( ( itemA , itemB ) => compare ( itemA [ 1 ] , itemB [ 1 ] ) ) ; if ( this . _doc && this . _id ) { this . _doc . dispatch ( [ { type : OpType . SetParentKey , id : item [ 0 ] . _id ! , parentKey : position , } , ] , [ { type : OpType . SetParentKey , id : item [ 0 ] . _id ! , parentKey : previousPosition , } , ] , [ this ] ) ; } } param index delete ( index ) { if ( index < 0 || index >= this . #items . length ) { throw new Error ( `Cannot delete list item at index "${ index }". index should be between 0 and ${ this . #items . length - 1 }` ) ; } const item = this . #items [ index ] ; item [ 0 ] . _detach ( ) ; this . #items . splice ( index , 1 ) ; if ( this . _doc ) { const childRecordId = item [ 0 ] . _id ; if ( childRecordId ) { this . _doc . dispatch ( [ { id : childRecordId , type : OpType . DeleteCrdt , } , ] , item [ 0 ] . _serialize ( this . _id ! , item [ 1 ] ) , [ this ] ) ; } } } /**
     * Returns an Array of all the elements in the LiveList.
     */ toArray ( ) { return this . #items . map ( ( entry ) => selfOrRegisterValue ( entry [ 0 ] ) ) ; } param predicate returns every ( predicate ) { return this . toArray ( ) . every ( predicate ) ; } param predicate returns filter ( predicate ) { return this . toArray ( ) . filter ( predicate ) ; } param predicate returns find ( predicate ) { return this . toArray ( ) . find ( predicate ) ; } param predicate returns findIndex ( predicate ) { return this . toArray ( ) . findIndex ( predicate ) ; } param callbackfn forEach ( callbackfn ) { return this . toArray ( ) . forEach ( callbackfn ) ; } param index returns get ( index ) { if ( index < 0 || index >= this . #items . length ) { return undefined ; } return selfOrRegisterValue ( this . #items [ index ] [ 0 ] ) ; } param searchElement param fromIndex returns indexOf ( searchElement , fromIndex ? ) { return this . toArray ( ) . indexOf ( searchElement , fromIndex ) ; } param searchElement param fromIndex returns lastIndexOf ( searchElement , fromIndex ? ) { return this . toArray ( ) . lastIndexOf ( searchElement , fromIndex ) ; } param callback returns map < U > ( callback ) { return this . #items . map ( ( entry , i ) => callback ( selfOrRegisterValue ( entry [ 0 ] ) , i ) ) ; } param predicate returns some ( predicate ) { return this . toArray ( ) . some ( predicate ) ; } [ Symbol . iterator ] ( ) { return new LiveListIterator ( this . #items ) ; } } class LiveListIterator < T > implements IterableIterator < T > { #innerIterator : IterableIterator < LiveListItem > ; constructor ( items ) { this . #innerIterator = items [ Symbol . iterator ] ( ) ; } [ Symbol . iterator ] ( ) { return this ; } next ( ) { const result = this . #innerIterator . next ( ) ; if ( result . done ) { return { done : true , value : undefined , } ; } return { value : selfOrRegisterValue ( result . value [ 0 ] ) , } ; } }