import { ArrayExt , StringExt } from '@lumino/algorithm' ; import { ReadonlyJSONValue } from '@lumino/coreutils' ; import { Field } from './field' ; import { createTriplexIds } from './utilities' ; /**
 * A field which represents a collaborative list of values.
 */ export class ListField < T extends ReadonlyJSONValue > extends Field < ListField . Value < T > , ListField . Update < T > , ListField . Metadata < T > , ListField . Change < T > , ListField . Patch < T > > { param options constructor ( options = { } ) { super ( options ) ; } /**
     * The discriminated type of the field.
     */ get type ( ) : 'list' { return 'list' ; } returns createValue ( ) { return [ ] ; } returns createMetadata ( ) { return { ids : [ ] , cemetery : { } } ; } param args returns applyUpdate ( args ) { let { previous , update , metadata , version , storeId } = args ; let clone = [ ... previous ] ; let change = [ ] ; let patch = [ ] ; if ( Private . isSplice ( update ) ) { update = [ update ] ; } for ( let splice of update ) { let obj = Private . applySplice ( clone , splice , metadata , version , storeId ) ; change . push ( obj . change ) ; patch . push ( obj . patch ) ; } return { value : clone , change , patch } ; } param args returns applyPatch ( args ) { let { previous , patch , metadata } = args ; let clone = [ ... previous ] ; let change = [ ] ; for ( let part of patch ) { let result = Private . applyPatch ( clone , part , metadata ) ; change . push ( ... result ) ; } return { value : clone , change } ; } param args returns unapplyPatch ( args ) { let { previous , patch , metadata } = args ; let clone = [ ... previous ] ; let change = [ ] ; for ( let part of patch ) { let reversed = { removedIds : part . insertedIds , insertedIds : part . removedIds , removedValues : part . insertedValues , insertedValues : part . removedValues } ; let result = Private . applyPatch ( clone , reversed , metadata ) ; change . push ( ... result ) ; } return { value : clone , change } ; } param first param second returns mergeChange ( first , second ) { return [ ... first , ... second ] ; } param first param second returns mergePatch ( first , second ) { return [ ... first , ... second ] ; } } /**
 * The namespace for the `ListField` class statics.
 */ export namespace ListField { /**
     * An options object for initializing a list field.
     */ export interface IOptions < T extends ReadonlyJSONValue > extends Field . IOptions { } /**
     * A type alias for the list field value type.
     */ export type Value < T extends ReadonlyJSONValue > = ReadonlyArray < T > ; /**
     * A type alias for a list field splice.
     */ export type Splice < T extends ReadonlyJSONValue > = { /**
         * The index of the splice.
         */ readonly index : number ; /**
         * The number of values to remove.
         */ readonly remove : number ; /**
         * The values to insert.
         */ readonly values : ReadonlyArray < T > ; } ; /**
     * A type alias for the list field update type.
     */ export type Update < T extends ReadonlyJSONValue > = Splice < T > | ReadonlyArray < Splice < T > > ; /**
     * A type alias for the list field metadata type.
     */ export type Metadata < T extends ReadonlyJSONValue > = { /**
         * An array of ids corresponding to the list elements.
         */ readonly ids : Array < string > ; /**
         * The cemetery for concurrently deleted elements.
         */ readonly cemetery : { [ id ] : number ; } ; } ; /**
     * A type alias for a list field change part.
     */ export type ChangePart < T extends ReadonlyJSONValue > = { /**
         * The index of the modification.
         */ readonly index : number ; /**
         * The values that were removed.
         */ readonly removed : ReadonlyArray < T > ; /**
         * The values that were inserted.
         */ readonly inserted : ReadonlyArray < T > ; } ; /**
     * A type alias for the list field change type.
     */ export type Change < T extends ReadonlyJSONValue > = ReadonlyArray < ChangePart < T > > ; /**
     * A type alias for the list field patch part.
     */ export type PatchPart < T extends ReadonlyJSONValue > = { /**
         * The ids that were removed.
         */ readonly removedIds : ReadonlyArray < string > ; /**
         * The values that were removed.
         */ readonly removedValues : ReadonlyArray < T > ; /**
         * The ids that were inserted.
         */ readonly insertedIds : ReadonlyArray < string > ; /**
         * The values that were inserted.
         */ readonly insertedValues : ReadonlyArray < T > ; } ; /**
     * A type alias for the list field patch type.
     */ export type Patch < T extends ReadonlyJSONValue > = ReadonlyArray < PatchPart < T > > ; } /**
 * The namespace for the module implementation details.
 */ namespace Private { /**
     * A type-guard function for a list field update type.
     */ export function isSplice < T extends ReadonlyJSONValue > ( value ) { return ! Array . isArray ( value ) ; } /**
     * A type alias for the result of a splice operation.
     */ export type SpliceResult < T extends ReadonlyJSONValue > = { /**
         * The user-facing change part for the splice.
         */ readonly change : ListField . ChangePart < T > ; /**
         * The system-facing patch part for the splice.
         */ readonly patch : ListField . PatchPart < T > ; } ; param array param splice param metadata param version param storeId returns export function applySplice < T extends ReadonlyJSONValue > ( array , splice , metadata , version , storeId ) { let { index , remove , values } = splice ; if ( index < 0 ) { index = Math . max ( 0 , index + array . length ) ; } else { index = Math . min ( index , array . length ) ; } let count = Math . min ( remove , array . length - index ) ; let lower = index === 0 ? '' : metadata . ids [ index - 1 ] ; let upper = index === array . length ? '' : metadata . ids [ index ] ; let ids = createTriplexIds ( values . length , version , storeId , lower , upper ) ; let removedIds = spliceArray ( metadata . ids , index , count , ids ) ; let removedValues = spliceArray ( array , index , count , values ) ; let change = { index , removed : removedValues , inserted : values } ; let patch = { removedIds , removedValues , insertedIds : ids , insertedValues : values } ; return { change , patch } ; } param value param patch param metadata returns export function applyPatch < T extends ReadonlyJSONValue > ( value , patch , metadata ) { let { removedIds , insertedIds , insertedValues } = patch ; let change = [ ] ; if ( removedIds . length > 0 ) { let chunks = findRemovedChunks ( removedIds , metadata ) ; while ( chunks . length > 0 ) { let { index , count } = chunks . pop ( ) ! ; metadata . ids . splice ( index , count ) ; let removed = value . splice ( index , count ) ; change . push ( { index , removed , inserted : [ ] } ) ; } } if ( insertedIds . length > 0 ) { let chunks = findInsertedChunks ( insertedIds , insertedValues , metadata ) ; while ( chunks . length > 0 ) { let { index , ids , values } = chunks . pop ( ) ! ; spliceArray ( metadata . ids , index , 0 , ids ) ; spliceArray ( value , index , 0 , values ) ; change . push ( { index , removed : [ ] , inserted : values } ) ; } } return change ; } /**
     * A type alias for a remove chunk.
     */ type RemoveChunk = { index : number ; count : number ; } ; param ids param metadata returns function findRemovedChunks ( ids , metadata ) { let chunks = [ ] ; let i = 0 ; let n = ids . length ; while ( i < n ) { let j = ArrayExt . lowerBound ( metadata . ids , ids [ i ] , StringExt . cmp ) ; if ( j === metadata . ids . length || metadata . ids [ j ] !== ids [ i ] ) { let count = metadata . cemetery [ ids [ i ] ] || 0 ; metadata . cemetery [ ids [ i ] ] = count + 1 ; i ++ ; continue ; } let index = j ; let count = 0 ; while ( i < n && StringExt . cmp ( ids [ i ] , metadata . ids [ j ] ) === 0 ) { count ++ ; i ++ ; j ++ ; } if ( count > 0 ) { chunks . push ( { index , count } ) ; } else { i ++ ; } } return chunks ; } /**
     * A type alias for an insert chunk.
     */ type InsertChunk < T extends ReadonlyJSONValue > = { index : number ; ids : string [ ] ; values : T [ ] ; } ; param ids param values param metadata returns function findInsertedChunks < T extends ReadonlyJSONValue > ( ids , values , metadata ) { let indices = [ ] ; let insertIds = [ ] ; let insertValues = [ ] ; for ( let i = 0 ; i < ids . length ; i ++ ) { if ( checkCemeteryForInsert ( ids [ i ] , metadata . cemetery ) ) { continue ; } insertIds . push ( ids [ i ] ) ; indices . push ( ArrayExt . lowerBound ( metadata . ids , ids [ i ] , StringExt . cmp ) ) ; insertValues . push ( values [ i ] ) ; } return chunkifyInsertions ( insertIds , insertValues , indices ) ; } param ids param values param indices returns function chunkifyInsertions < T extends ReadonlyJSONValue > ( ids , values , indices ) { let chunks = [ ] ; let insertIndex ; let i = 0 ; while ( i < ids . length ) { let chunkIds = [ ] ; let chunkValues = [ ] ; insertIndex = indices [ i ] ; while ( indices [ i ] === insertIndex && i < ids . length ) { chunkIds . push ( ids [ i ] ) ; chunkValues . push ( values [ i ] ) ; i ++ ; } if ( chunkValues . length ) { chunks . push ( { index : insertIndex , ids : chunkIds , values : chunkValues } ) ; } } return chunks ; } param id param cemetery returns function checkCemeteryForInsert ( id , cemetery ) { let count = cemetery [ id ] || 0 ; if ( count === 1 ) { delete cemetery [ id ] ; return true ; } if ( count > 1 ) { cemetery [ id ] = count - 1 ; return true ; } return false ; } param arr param start param deleteCount param items returns function spliceArray < T > ( arr , start , deleteCount ? , items ? ) { if ( ! items ) { return arr . splice ( start , deleteCount ) ; } let size = 100000 ; if ( items . length < size ) { return arr . splice ( start , deleteCount || 0 , ... items ) ; } let deleted = arr . splice ( start , deleteCount ) ; let n = Math . floor ( items . length / size ) ; let idx = 0 ; for ( let i = 0 ; i < n ; i ++ , idx += size ) { arr . splice ( start + idx , 0 , ... items . slice ( idx , idx + size ) ) ; } arr . splice ( start + idx , 0 , ... items . slice ( idx ) ) ; return deleted ; } }