import * as React from 'react'
import { pipe } from 'fp-ts/function'
import * as O from 'fp-ts/Option'
import * as Task from 'fp-ts/Task'
import * as TE from 'fp-ts/TaskEither'
import * as T from 'fp-ts/Tree'
import * as TF from '@monorail/sharedHelpers/fp-ts-ext/Tree'
import { isNotNil } from '@monorail/sharedHelpers/typeGuards'
import { MenuAction } from '../actionsMenu/ActionsMenu'
import { ActionReturn } from './TreeView'
const negateCurried2 = (b: B) => boolean>(f: F) => (
a: A,
) => (b: B) => !f(a)(b)
const ifNode = (condition: boolean) => (
action: MenuAction,
): MenuAction | undefined => (condition ? action : undefined)
type ForestFunctions = {
forest: T.Forest
addChild: (
makeChild: (fromNode: T.Tree) => TE.TaskEither>,
) => (key: K) => TE.TaskEither>
addRoot: (newRoot: A) => void
duplicate: (
key: K,
) => (
modify: (a: A) => TE.TaskEither,
) => TE.TaskEither>
remove: (key: K) => void
moveUp: (key: K) => O.Option>
moveDown: (key: K) => O.Option>
moveInto: (key: K) => O.Option>
canMoveUp: (key: K) => boolean
canMoveDown: (key: K) => boolean
canMoveInto: (key: K) => boolean
}
export const useForestFunctions = (
forest: T.Forest,
getKey: (a: A) => K,
): ForestFunctions => {
const addChild = React.useCallback(
(mc: (fromNode: T.Tree) => TE.TaskEither>) => (key: K) =>
pipe(
forest,
TF.addChildWhereAsync((t: T.Tree) => getKey(t.value) === key)(mc),
),
[forest, getKey],
)
const addRoot = React.useCallback(
(node: A) => pipe(forest, TF.addRoot(node)),
[forest],
)
const duplicate = React.useCallback(
(key: K) => (modify: (a: A) => TE.TaskEither) =>
pipe(
forest,
TF.duplicateWhereAsync((t: T.Tree) => getKey(t.value) === key)(
modify,
),
),
[forest, getKey],
)
const remove = React.useCallback(
(key: K) =>
pipe(
forest,
TF.removeWhere((t: T.Tree) => getKey(t.value) === key),
),
[forest, getKey],
)
const move = React.useCallback(
(
func: typeof TF['moveInto'],
fallback: typeof TF['moveInto'] = () => () => O.none,
) => (key: K) =>
pipe(
forest,
func(t => getKey(t.value) === key),
O.alt(() =>
pipe(
forest,
fallback(t => getKey(t.value) === key),
),
),
),
[forest, getKey],
)
const moveUp = React.useCallback(
(key: K) => move(TF.moveLeft, TF.moveUpBefore)(key),
[move],
)
const moveDown = React.useCallback(
(key: K) => move(TF.moveRight, TF.moveUpAfter)(key),
[move],
)
const moveInto = React.useCallback((key: K) => move(TF.moveInto)(key), [move])
const canMove = React.useCallback(
(
check: typeof TF['isLeftmost'],
secondaryCheck: typeof TF['isLeftmost'] = () => () => false,
) => (key: K) =>
pipe(
forest,
check(t => getKey(t.value) === key),
c =>
c ||
pipe(
forest,
secondaryCheck(t => getKey(t.value) === key),
),
),
[forest, getKey],
)
const canMoveUp = React.useCallback(
(key: K) => canMove(negateCurried2(TF.isLeftmost), TF.hasParent)(key),
[canMove],
)
const canMoveDown = React.useCallback(
(key: K) => canMove(negateCurried2(TF.isRightmost), TF.hasParent)(key),
[canMove],
)
const canMoveInto = React.useCallback(
(key: K) => canMove(negateCurried2(TF.isRightmost))(key),
[canMove],
)
const res = React.useMemo(
() => ({
forest,
addChild,
addRoot,
duplicate,
remove,
moveUp,
moveDown,
moveInto,
canMoveUp,
canMoveDown,
canMoveInto,
}),
[
addChild,
addRoot,
canMoveDown,
canMoveInto,
canMoveUp,
duplicate,
forest,
moveDown,
moveInto,
moveUp,
remove,
],
)
return res
}
export const useTreeViewCreateChildAction = (
{ addChild }: Pick, 'addChild'>,
createNode: (fromNode: T.Tree) => TE.TaskEither>,
) =>
React.useCallback(
(key: K): MenuAction> => ({
label: 'Create Child',
iconName: 'add_box',
onClick: onClickParent =>
pipe(
key,
addChild(createNode),
Task.map(() => onClickParent()),
Task.map(() => ({ tag: 'nodeOpen', key, nodeOpen: true })),
),
}),
[addChild, createNode],
)
type MovementChecks = 'canMoveDown' | 'canMoveInto' | 'canMoveUp'
type MovementFuncNames = 'moveUp' | 'moveDown' | 'moveInto'
type MovementFuncs = { [F in MovementFuncNames]: (key: K) => void }
export const useTreeViewMoveActions = (
ffs: Pick, MovementChecks> & MovementFuncs,
): ((key: K) => Array>>) => {
const {
canMoveUp,
canMoveDown,
canMoveInto,
moveUp,
moveDown,
moveInto,
} = ffs
const getMoveActions = React.useCallback(
(key: K) =>
[
ifNode(canMoveUp(key))({
label: 'Move Up',
iconName: 'arrow_upward',
onClick: onClickParent => {
moveUp(key)
onClickParent()
},
}),
ifNode(canMoveDown(key))({
label: 'Move Down',
iconName: 'arrow_downward',
onClick: onClickParent => {
moveDown(key)
onClickParent()
},
}),
ifNode(canMoveInto(key))({
label: 'Move Into',
iconName: 'arrow_forward',
onClick: onClickParent => {
moveInto(key)
onClickParent()
},
}),
].filter(isNotNil),
[canMoveUp, canMoveDown, canMoveInto, moveUp, moveDown, moveInto],
)
return getMoveActions
}
export const useTreeViewActions = (
ffs: Pick, MovementChecks | 'addChild'> &
MovementFuncs,
createNode: (fromNode: T.Tree) => TE.TaskEither>,
): ((key: K) => Array>>) => {
const create = useTreeViewCreateChildAction(ffs, createNode)
const moves = useTreeViewMoveActions(ffs)
const getActions = React.useCallback(
(key: K): Array>> => {
const keyedMoves = moves(key)
return keyedMoves.length
? [create(key), { type: 'divider' }, ...keyedMoves]
: [create(key)]
},
[create, moves],
)
return getActions
}