import remove from 'lodash.remove' ; import findIndex from 'lodash.findindex' ; import filter from 'lodash.filter' ; import orderBy from 'lodash.orderby' ; import uniqBy from 'lodash.uniqby' ; import groupBy from 'lodash.groupby' ; import Model , { IAction } from './model' ; import Errors from './errors' ; type Constructor < T > = new ( ... args ) => T ; import { chunk , nth , IGrouped , collectionPredictor , treatPredicatePickNode , treatPredicateSortNode , TPredicatePickNode , TPredicatePickKey , TOrderSort , TPredicateSort } from './lodash-utils' ; import { isCollectionInstance } from './lib' ; export default class Collection extends Model { constructor ( list = [ ] , models , ... props ) { super ( [ ] , Object . assign ( { } , ... props , { nodeModel : models [ 0 ] , collectionModel : models [ 1 ] } ) ) ; if ( isCollectionInstance ( this . newNode ( undefined ) ) ) throw Errors . forbiddenMultiDimCollection ( ) ; const assignWithStorage = async ( ) => { if ( ! this . is ( ) . connected ( ) || ! ( await this . localStore ( ) . get ( ) ) ) { this . setState ( list ) ; } } ; assignWithStorage ( ) ; } public arrayOf = ( key ) : any [ ] => this . map ( ( m ) => m . state [ key ] ) ; public append = ( values ) : IAction => this . concat ( values ) ; public chunk = ( nChunk ) : Collection [ ] => { const list = chunk ( this . state , nChunk ) ; for ( let i = 0 ; i < list . length ; i ++ ) { list [ i ] = this . newCollection ( list [ i ] ) as Collection ; } return list ; } ; public concat = ( list ) : IAction => { const newList = this . state . slice ( ) . concat ( this . to ( ) . listClass ( list ) ) ; this . setState ( newList ) ; return this . action ( list . length ) ; } ; public count = ( ) : number => this . state . length ; public copy = ( ) : Collection => this . newCollection ( this . state . slice ( ) ) ; public delete = ( v ) : IAction => { const index = this . indexOf ( v ) ; const list = this . state . slice ( ) ; if ( index > - 1 ) { const v = list . splice ( index , 1 ) ; if ( ! ! v . length ) { this . setState ( list ) ; return this . action ( v [ 0 ] ) ; } } return this . action ( ) ; } ; public deleteAll = ( list = this . state ) => { let count = 0 ; list . map ( ( e ) => this . indexOf ( e ) != - 1 && this . delete ( e ) && count ++ ) ; return this . action ( count ) ; } ; public deleteBy = ( predicate ) : IAction => { const futureState = collectionPredictor ( predicate , this ) ; const e = remove ( futureState , predicate ) ; ! ! e . length && this . setState ( futureState ) ; return this . action ( ) ; } ; public deleteIndex = ( index ) : IAction => { const { value } = this . splice ( index , 1 ) ; return this . action ( ! ! value . length ? value [ 0 ] : undefined ) ; } ; public find = ( predicate ) : Model | undefined => { const index = this . findIndex ( predicate ) ; if ( index == - 1 ) return undefined ; return this . nodeAt ( index ) ; } ; public findIndex = ( predicate ) : number => findIndex ( collectionPredictor ( predicate , this ) , predicate ) ; public filter = ( predicate ) : Collection => this . newCollection ( filter ( this . state , treatPredicatePickNode ( predicate ) ) ) ; public filterIn = ( predicate , arrayElems ) : Collection => { const value = ( m , index ) => typeof predicate === 'string' ? m . state [ predicate ] : predicate ( m , index ) ; return this . filter ( ( m , index ) => arrayElems . indexOf ( value ( m , index ) ) != - 1 ) ; } ; public first = ( ) : Model | undefined => this . nodeAt ( 0 ) ; public forEach = ( callback ) => { for ( let i = 0 ; i < this . count ( ) ; i ++ ) callback ( this . state [ i ] , i ) ; } ; public groupBy = ( predicate ) : IGrouped => { const d = groupBy ( this . state , treatPredicatePickNode ( predicate ) ) ; const ret = { } ; Object . keys ( d ) . map ( ( key ) => { ret [ key ] = this . newCollection ( d [ key ] ) ; } ) ; return ret ; } ; public indexOf = ( v ) : number => findIndex ( this . to ( ) . plain ( ) , this . newNode ( v ) . to ( ) . plain ( ) ) ; public last = ( ) : Model | undefined => this . nodeAt ( this . count ( ) - 1 ) ; public limit = ( limit ) : Collection => this . slice ( 0 , limit ) ; public map = ( callback ) : any [ ] => { let ret = [ ] ; this . forEach ( ( m , index ) => { const v = callback ( m , index ) ; v && ret . push ( v ) ; } ) ; return ret ; } ; public newCollection = ( v ) : Collection => Collection . IsCollection ( v ) ? v : this . _newCollectionModelInstance ( v ) ; public newNode = ( v ) : Model => Model . IsModel ( v ) ? v : this . _newNodeModelInstance ( v ) ; public nodeAt = ( index ) : Model | undefined => this . state [ index ] ? this . state [ index ] : undefined ; public nth = ( index ) : Model | undefined => this . count ( ) == 0 ? undefined : ( nth ( this . state , index ) as Model ) ; public offset = ( offset ) : Collection => this . slice ( offset ) ; public orderBy = ( predicate , order ) : Collection => { return this . newCollection ( orderBy ( this . state , treatPredicateSortNode ( predicate ) , order ) ) ; } ; public pop = ( ) : IAction => { const list = this . state . slice ( ) ; const poped = list . pop ( ) ; poped && this . setState ( list ) ; return this . action ( poped ) ; } ; public prepend = ( list ) : IAction => { const newList = this . to ( ) . listClass ( list ) . concat ( this . state . slice ( ) ) ; this . setState ( newList ) ; return this . action ( list . length ) ; } ; public push = ( v ) : IAction => { const list = this . state . slice ( ) ; const n = list . push ( this . newNode ( v ) ) ; n && this . setState ( list ) ; return this . action ( n ) ; } ; public reduce = ( callback , initialAccumulator = this . count ( ) ? this . nodeAt ( 0 ) : null ) => { this . forEach ( ( m ) => initialAccumulator = callback ( initialAccumulator , m ) ) ; return initialAccumulator ; } ; public reverse = ( ) : Collection => this . newCollection ( this . state . slice ( ) . reverse ( ) ) ; public shift = ( ) : IAction => { const list = this . state . slice ( ) ; const shifted = list . shift ( ) ; shifted && this . setState ( list ) ; return this . action ( shifted ) ; } ; public slice = ( ... indexes ) : Collection => this . newCollection ( this . state . slice ( ... indexes ) ) ; public splice = ( ... args ) : IAction => { const start = args [ 0 ] ; const deleteCount = args [ 1 ] ; const items = args . slice ( 2 , args . length ) ; const internalSplice = ( ... args ) => { const list = this . state . slice ( ) ; const value = list . splice ( ... args ) ; this . setState ( list ) ; return this . action ( value ) ; } ; if ( typeof start !== 'number' ) throw new Error ( "splice start parameter must be a number" ) ; if ( ! deleteCount ) return internalSplice ( start ) ; if ( typeof deleteCount !== 'number' ) throw new Error ( "splice deleteCount parameter must be a number" ) ; if ( items . length == 0 ) return internalSplice ( start , deleteCount ) ; for ( let i = 0 ; i < items . length ; i ++ ) { if ( ! Model . IsModel ( items [ i ] ) && ! Model . _isObject ( items [ i ] ) ) throw new Error ( "items parameter must be an Objet or the same Model than collection's nodes" ) ; else items [ i ] = this . newNode ( items [ i ] ) ; } return internalSplice ( start , deleteCount , ... items ) ; } ; public updateAll = ( toSet ) : IAction => { this . forEach ( ( m ) => m . setState ( toSet ) ) ; return this . action ( this . count ( ) ) ; } ; public updateAt = ( v , index ) : IAction => { const vCopy = this . newNode ( v ) ; const list = this . state . slice ( ) ; if ( list [ index ] ) { list [ index ] = vCopy ; return this . setState ( list ) ; } return this . push ( vCopy ) ; } ; public updateWhere = ( predicate , toSet ) : IAction => { const list = this . filter ( predicate ) ; list . forEach ( ( m ) => m . setState ( toSet ) ) ; return list . action ( list . count ( ) ) ; } ; public uniq = ( ) : Collection => { let ret = this . newCollection ( [ ] ) ; this . forEach ( ( m ) => ! ret . find ( m . to ( ) . plain ( ) ) && ret . push ( m ) ) ; return ret ; } ; public uniqBy = ( predicate ) : Collection => this . newCollection ( uniqBy ( collectionPredictor ( predicate , this ) , predicate ) ) ; private _getCollectionModel = ( ) : any => this . super ( ) . option ( ) . collectionModel ( ) as Collection ; private _getNodeModel = ( ) : any => this . super ( ) . option ( ) . nodeModel ( ) as Model ; private _newCollectionModelInstance = ( defaultState ) => new ( this . _getCollectionModel ( ) ) ( defaultState , this . kids ( ) ) ; private _newNodeModelInstance = ( defaultState ) => new ( this . _getNodeModel ( ) ) ( defaultState , this . kids ( ) ) ; static IsCollection = ( value ) : boolean => isCollectionInstance ( value ) ; }