import { Action , combineReducers } from 'redux' ; import { omit , without } from 'lodash' ; import { AnyAppProperties , AnyProperties , DriveMetadata } from '../util/googleDriveUtils' ; import { buildTutorialMetadata } from '../tutorial/tutorialUtils' ; export enum FileIndexActionTypes { ADD_FILES_ACTION = 'add-files-action' , ADD_ROOT_FILES_ACTION = 'add-root-files-action' , REMOVE_FILE_ACTION = 'remove-file-action' , UPDATE_FILE_ACTION = 'update-file-action' , REPLACE_FILE_ACTION = 'replace-file-action' } export interface AddRootFilesActionType extends Action { type : FileIndexActionTypes . ADD_ROOT_FILES_ACTION ; files : DriveMetadata [ ] ; } interface AddFilesActionType extends Action { type : FileIndexActionTypes . ADD_FILES_ACTION ; files : DriveMetadata [ ] ; } export function addFilesAction ( files ) { return { type : FileIndexActionTypes . ADD_FILES_ACTION , files } ; } export function addRootFilesAction ( files ) { return { type : FileIndexActionTypes . ADD_ROOT_FILES_ACTION , files } ; } export interface RemoveFileActionType extends Action { type : FileIndexActionTypes . REMOVE_FILE_ACTION ; file : { id : string ; } & Partial < DriveMetadata > ; peerKey : string ; } export function removeFileAction ( file ) { return { type : FileIndexActionTypes . REMOVE_FILE_ACTION , file , peerKey : file . id } ; } export interface UpdateFileActionType < T = AnyAppProperties , U = AnyProperties > extends Action { type : FileIndexActionTypes . UPDATE_FILE_ACTION ; metadata : DriveMetadata < T , U > ; peerKey : string | null ; } export function updateFileAction ( metadata , peerKey = null ) { return { type : FileIndexActionTypes . UPDATE_FILE_ACTION , metadata , peerKey } ; } export interface ReplaceFileAction < T = AnyAppProperties , U = AnyProperties > extends Action { type : FileIndexActionTypes . REPLACE_FILE_ACTION ; metadata : DriveMetadata < T , U > ; newMetadata : DriveMetadata < T , U > ; rootFolder : string ; } export function replaceFileAction ( metadata , newMetadata , rootFolder ) { return { type : FileIndexActionTypes . REPLACE_FILE_ACTION , metadata , newMetadata , rootFolder } ; } export const ERROR_FILE_NAME = 'image error' ; export function setFileErrorAction ( metadataId ) { return { type : FileIndexActionTypes . UPDATE_FILE_ACTION , metadata : { id : metadataId , name : ERROR_FILE_NAME , properties : { width : 1 , height : 1 } , parents : [ ] } } ; } export function setFileContinueAction ( metadataId ) { return { type : FileIndexActionTypes . UPDATE_FILE_ACTION , metadata : { id : metadataId , name : 'missing image' , properties : { width : 1 , height : 1 } } } ; } type FileIndexActionType = AddRootFilesActionType | AddFilesActionType | RemoveFileActionType | UpdateFileActionType | ReplaceFileAction ; type DriveMetadataReducerType = { [ key ] : DriveMetadata ; } ; function driveMetadataReducer ( state = buildTutorialMetadata ( ) , action ) { switch ( action . type ) { case FileIndexActionTypes . ADD_FILES_ACTION : case FileIndexActionTypes . ADD_ROOT_FILES_ACTION : return action . files . reduce ( ( all , file ) => ( { ... all , [ file . id ] : file } ) , state ) ; case FileIndexActionTypes . REMOVE_FILE_ACTION : return omit ( state , action . file . id ) ; case FileIndexActionTypes . UPDATE_FILE_ACTION : return { ... state , [ action . metadata . id ] : action . metadata } ; case FileIndexActionTypes . REPLACE_FILE_ACTION : const childrenIds = Object . keys ( state ) . filter ( ( id ) => ( state [ id ] . parents . indexOf ( action . metadata . id ) >= 0 ) ) ; return { ... omit ( state , action . metadata . id ) , [ action . newMetadata . id ] : { ... state [ action . metadata . id ] , ... action . newMetadata } , ... childrenIds . reduce ( ( children , id ) => { children [ id ] = { ... state [ id ] , parents : state [ id ] . parents . map ( ( parentId ) => ( parentId === action . metadata . id ? action . newMetadata . id : parentId ) ) } ; return children ; } , { } ) } ; default : return state ; } } type ChildrenReducerType = { [ key ] : string [ ] ; } ; function childrenReducer ( state = { } , action ) { switch ( action . type ) { case FileIndexActionTypes . ADD_FILES_ACTION : case FileIndexActionTypes . ADD_ROOT_FILES_ACTION : return action . files . reduce ( ( result , file ) => { file . parents && file . parents . forEach ( ( parent ) => { const previous = result [ parent ] || [ ] ; if ( previous . indexOf ( file . id ) < 0 ) { result [ parent ] = [ ... previous , file . id ] ; } } ) ; return result ; } , { ... state } ) ; case FileIndexActionTypes . UPDATE_FILE_ACTION : return ( action . metadata . parents || [ ] ) . reduce < ChildrenReducerType | undefined > ( ( nextState , parentId ) => { if ( ! state [ parentId ] || state [ parentId ] . indexOf ( action . metadata . id ) < 0 ) { nextState = nextState || { ... state } ; const previous = nextState [ parentId ] || [ ] ; nextState [ parentId ] = [ ... previous , action . metadata . id ] ; } return nextState ; } , undefined ) || state ; case FileIndexActionTypes . REMOVE_FILE_ACTION : let result = { ... state } ; delete ( result [ action . file . id ] ) ; action . file . parents && action . file . parents . forEach ( ( parent ) => { result [ parent ] = without ( result [ parent ] , action . file . id ) ; } ) ; return result ; case FileIndexActionTypes . REPLACE_FILE_ACTION : return { ... omit ( state , action . metadata . id ) , [ action . newMetadata . id ] : state [ action . metadata . id ] , ... action . newMetadata . parents . reduce ( ( all , id ) => { const previous = without ( state [ id ] || [ ] , action . metadata . id ) ; if ( previous . indexOf ( action . newMetadata . id ) < 0 ) { all [ id ] = [ ... previous , action . newMetadata . id ] ; } return all ; } , { } ) } ; default : return state ; } } type RootsReducerType = { [ key ] : string ; } ; function rootsReducer ( state = { } , action ) { switch ( action . type ) { case FileIndexActionTypes . ADD_ROOT_FILES_ACTION : return action . files . reduce ( ( result , file ) => ( { ... result , [ file . name ] : file . id } ) , state ) ; default : return state ; } } export interface FileIndexReducerType { driveMetadata : DriveMetadataReducerType ; children : ChildrenReducerType ; roots : RootsReducerType ; } const combinedFileIndexReducer = combineReducers < FileIndexReducerType > ( { driveMetadata : driveMetadataReducer , children : childrenReducer , roots : rootsReducer } ) ; export default function fileIndexReducer ( state , action ) { let nextState = combinedFileIndexReducer ( state , action ) ; if ( action . type === FileIndexActionTypes . UPDATE_FILE_ACTION ) { const id = action . metadata . id ; if ( state && state . driveMetadata [ id ] && nextState . driveMetadata [ id ] && state . driveMetadata [ id ] . parents !== nextState . driveMetadata [ id ] . parents ) { const removedParents = without ( state . driveMetadata [ id ] . parents , ... nextState . driveMetadata [ id ] . parents ) ; if ( removedParents . length > 0 ) { const children = { ... nextState . children } ; for ( let removedParentId of removedParents ) { children [ removedParentId ] = without ( children [ removedParentId ] , id ) ; } nextState = { ... nextState , children } ; } } } return nextState ; }