import { Store } from 'redux' import { addSource, addTarget, removeSource, removeTarget, } from './actions/registry' import getNextUniqueId from './utils/getNextUniqueId' import { State } from './reducers' import { DragSource, DropTarget, SourceType, TargetType, Identifier, HandlerRole, HandlerRegistry, } from './interfaces' import { validateSourceContract, validateTargetContract, validateType, } from './contracts' const invariant = require('invariant') const asap = require('asap') function getNextHandlerId(role: HandlerRole): string { const id = getNextUniqueId().toString() switch (role) { case HandlerRole.SOURCE: return `S${id}` case HandlerRole.TARGET: return `T${id}` default: throw new Error(`Unknown Handler Role: ${role}`) } } function parseRoleFromHandlerId(handlerId: string) { switch (handlerId[0]) { case 'S': return HandlerRole.SOURCE case 'T': return HandlerRole.TARGET default: invariant(false, `Cannot parse handler ID: ${handlerId}`) } } function mapContainsValue(map: Map, searchValue: T) { const entries = map.entries() let isDone = false do { const { done, value: [, value] } = entries.next() if (value === searchValue) { return true } isDone = done } while (!isDone) return false } export default class HandlerRegistryImpl implements HandlerRegistry { private types: Map = new Map() private dragSources: Map = new Map() private dropTargets: Map = new Map() private pinnedSourceId: string | null = null private pinnedSource: any = null constructor(private store: Store) {} public addSource(type: SourceType, source: DragSource) { validateType(type) validateSourceContract(source) const sourceId = this.addHandler(HandlerRole.SOURCE, type, source) this.store.dispatch(addSource(sourceId)) return sourceId } public addTarget(type: TargetType, target: DropTarget) { validateType(type, true) validateTargetContract(target) const targetId = this.addHandler(HandlerRole.TARGET, type, target) this.store.dispatch(addTarget(targetId)) return targetId } public containsHandler(handler: DragSource | DropTarget) { return ( mapContainsValue(this.dragSources, handler) || mapContainsValue(this.dropTargets, handler) ) } public getSource(sourceId: string, includePinned = false): DragSource { invariant(this.isSourceId(sourceId), 'Expected a valid source ID.') const isPinned = includePinned && sourceId === this.pinnedSourceId const source = isPinned ? this.pinnedSource : this.dragSources.get(sourceId) return source } public getTarget(targetId: string): DropTarget { invariant(this.isTargetId(targetId), 'Expected a valid target ID.') return this.dropTargets.get(targetId) as DropTarget } public getSourceType(sourceId: string) { invariant(this.isSourceId(sourceId), 'Expected a valid source ID.') return this.types.get(sourceId) as Identifier } public getTargetType(targetId: string): Identifier | Identifier[] { invariant(this.isTargetId(targetId), 'Expected a valid target ID.') return this.types.get(targetId) as Identifier | Identifier[] } public isSourceId(handlerId: string) { const role = parseRoleFromHandlerId(handlerId) return role === HandlerRole.SOURCE } public isTargetId(handlerId: string) { const role = parseRoleFromHandlerId(handlerId) return role === HandlerRole.TARGET } public removeSource(sourceId: string) { invariant(this.getSource(sourceId), 'Expected an existing source.') this.store.dispatch(removeSource(sourceId)) asap(() => { this.dragSources.delete(sourceId) this.types.delete(sourceId) }) } public removeTarget(targetId: string) { invariant(this.getTarget(targetId), 'Expected an existing target.') this.store.dispatch(removeTarget(targetId)) this.dropTargets.delete(targetId) this.types.delete(targetId) } public pinSource(sourceId: string) { const source = this.getSource(sourceId) invariant(source, 'Expected an existing source.') this.pinnedSourceId = sourceId this.pinnedSource = source } public unpinSource() { invariant(this.pinnedSource, 'No source is pinned at the time.') this.pinnedSourceId = null this.pinnedSource = null } private addHandler( role: HandlerRole, type: SourceType | TargetType, handler: DragSource | DropTarget, ): string { const id = getNextHandlerId(role) this.types.set(id, type) if (role === HandlerRole.SOURCE) { this.dragSources.set(id, handler as DragSource) } else if (role === HandlerRole.TARGET) { this.dropTargets.set(id, handler as DropTarget) } return id } }