Files
AFFiNE/libs/components/editor-core/src/hooks.ts
2022-08-05 22:44:01 +08:00

224 lines
5.6 KiB
TypeScript

import { noop, Point } from '@toeverything/utils';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useEditor } from './Contexts';
import {
AsyncBlock,
BlockEditor,
SelectEventTypes,
SelectionInfo,
SelectionSettingsMap,
} from './editor';
function useRequestReRender() {
const [, setUpdateCounter] = useState(0);
const animationFrameRef = useRef<number | null>(null);
const requestReRender = useCallback((immediate = false) => {
if (animationFrameRef.current && !immediate) {
return;
}
if (!immediate) {
animationFrameRef.current = requestAnimationFrame(() => {
setUpdateCounter(state => state + 1);
animationFrameRef.current = null;
});
return;
}
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
animationFrameRef.current = null;
}
setUpdateCounter(state => state + 1);
}, []);
useEffect(() => {
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
animationFrameRef.current = null;
}
});
useEffect(
() => () => {
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
}
},
[]
);
return requestReRender;
}
export const useBlock = (blockId: string) => {
const [block, setBlock] = useState<AsyncBlock>();
const requestReRender = useRequestReRender();
const { editor } = useEditor();
useEffect(() => {
if (!blockId) {
return undefined;
}
let valid = true;
let offUpdate = noop;
editor.getBlockById(blockId).then(node => {
if (!valid) {
return;
}
if (!node) {
console.warn('Failed to get block by id', blockId);
return undefined;
}
setBlock(node);
offUpdate = node.onUpdate(() => {
requestReRender();
});
});
return () => {
valid = false;
offUpdate();
};
}, [blockId, editor, requestReRender]);
return { block };
};
/**
*
* hooks run when block node selected as a block
* @export
*/
export const useOnSelect = (
blockId: string,
cb: (isSelect: boolean) => void
) => {
const { editor } = useEditor();
useEffect(() => {
editor.selectionManager.observe(blockId, SelectEventTypes.onSelect, cb);
return () => {
editor.selectionManager.unobserve(
blockId,
SelectEventTypes.onSelect,
cb
);
};
}, [cb, blockId, editor]);
};
/**
*
* hooks run when block was set focused by selection manager
* @export
*/
export const useOnSelectActive = (
blockId: string,
cb: (position: Point | undefined) => void
) => {
const { editor } = useEditor();
useEffect(() => {
editor.selectionManager.observe(blockId, SelectEventTypes.active, cb);
return () => {
editor.selectionManager.unobserve(
blockId,
SelectEventTypes.active,
cb
);
};
}, [cb, blockId, editor]);
};
/**
*
* hooks run when block was set range by selection manager
* @export
*/
export const useOnSelectSetSelection = <T extends keyof SelectionSettingsMap>(
blockId: string,
cb: (args: SelectionSettingsMap[T]) => void
) => {
const { editor } = useEditor();
useEffect(() => {
editor.selectionManager.observe(
blockId,
SelectEventTypes.setSelection,
cb
);
return () => {
editor.selectionManager.unobserve(
blockId,
SelectEventTypes.setSelection,
cb
);
};
}, [cb, blockId, editor]);
};
/**
*
* hooks run when selection type or range is changed
* @export
*/
export const useOnSelectChange = (cb: (info: SelectionInfo) => void) => {
const { editor } = useEditor();
useEffect(() => {
editor.selectionManager.onSelectionChange(cb);
return () => {
editor.selectionManager.unBindSelectionChange(cb);
};
}, [editor, cb]);
};
/**
*
* hooks run when select end (based on mouse up)
* @export
*/
export const useOnSelectEnd = (cb: (info: SelectionInfo) => void) => {
const { editor } = useEditor();
useEffect(() => {
editor.selectionManager.onSelectEnd(cb);
return () => {
editor.selectionManager.onSelectEnd(cb);
};
}, [editor, cb]);
};
/**
*
* hooks run when select range start with the block node
* @export
*/
export const useOnSelectStartWith = (
blockId: string,
cb: (args: MouseEvent) => void
) => {
const { editor } = useEditor();
useEffect(() => {
editor.mouseManager.onSelectStartWith(blockId, cb);
return () => {
editor.mouseManager.offSelectStartWith(blockId, cb);
};
}, [editor, cb, blockId]);
};
/**
*
* hooks run when drag state change
* @export
*/
export const useIsOnDrag = (editor: BlockEditor) => {
const editorInstance = editor;
const [isOnDrag, setIsOnDrag] = useState(false);
useEffect(() => {
const callback = (isDrag: boolean) => setIsOnDrag(isDrag);
editorInstance.dragDropManager.onDragStateChange(callback);
return () => {
editorInstance.dragDropManager.offDragStateChange(callback);
};
}, [editorInstance]);
return isOnDrag;
};