feat(core): add onError hook

Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com>
This commit is contained in:
braks
2023-03-23 15:58:30 +01:00
committed by Braks
parent 87249e5dde
commit 8c3f54ec28
22 changed files with 250 additions and 180 deletions
@@ -0,0 +1,49 @@
import type { Ref } from 'vue'
import type { EventHook } from '@vueuse/core'
/**
* Source code taken from https://github.com/vueuse/vueuse/blob/main/packages/shared/createEventHook/index.ts
*
* Modified to be able to check if there are any event listeners
*/
export interface EventHookExtended<T> extends EventHook<T> {
fns: Ref<Set<(param: T) => void>>
}
export function createExtendedEventHook<T = any>(defaultHandler: (param: T) => void = () => {}): EventHookExtended<T> {
const fns = ref(new Set<(param: T) => void>())
if (defaultHandler) {
fns.value.add(defaultHandler)
}
const off = (fn: (param: T) => void) => {
fns.value.delete(fn)
}
const on = (fn: (param: T) => void) => {
if (fns.value.has(defaultHandler)) {
fns.value.delete(defaultHandler)
}
fns.value.add(fn)
const offFn = () => off(fn)
tryOnScopeDispose(offFn)
return {
off: offFn,
}
}
const trigger = (param: T) => {
return Promise.all(Array.from(fns.value).map((fn) => fn(param)))
}
return {
on,
off,
trigger,
fns,
}
}
+10 -4
View File
@@ -1,5 +1,5 @@
import { isNumber } from '@vueuse/shared'
import type { Actions, CoordinateExtent, ExtendedParentExtent, GraphNode, NodeDragItem, XYPosition } from '~/types'
import type { Actions, CoordinateExtent, ExtendedParentExtent, GraphNode, NodeDragItem, State, XYPosition } from '~/types'
export function hasSelector(target: Element, selector: string, node: Element): boolean {
let current = target
@@ -97,7 +97,12 @@ function getParentExtent(
return false
}
export function getExtent<T extends NodeDragItem | GraphNode>(item: T, extent?: CoordinateExtent, parent?: GraphNode) {
export function getExtent<T extends NodeDragItem | GraphNode>(
item: T,
onError: State['hooks']['error']['trigger'],
extent?: CoordinateExtent,
parent?: GraphNode,
) {
let currentExtent = item.extent || extent
if (item.extent === 'parent' || (!Array.isArray(item.extent) && item.extent?.range === 'parent')) {
@@ -108,7 +113,7 @@ export function getExtent<T extends NodeDragItem | GraphNode>(item: T, extent?:
currentExtent = parentExtent
}
} else {
warn('Only child nodes can use a parent extent.')
onError(new VueFlowError(ErrorCode.NODE_EXTENT_INVALID, item.id))
currentExtent = extent
}
@@ -127,10 +132,11 @@ export function getExtent<T extends NodeDragItem | GraphNode>(item: T, extent?:
export function calcNextPosition(
node: GraphNode | NodeDragItem,
nextPosition: XYPosition,
onError: State['hooks']['error']['trigger'],
nodeExtent?: CoordinateExtent,
parentNode?: GraphNode,
) {
const extent = getExtent(node, nodeExtent, parentNode)
const extent = getExtent(node, onError, nodeExtent, parentNode)
const clampedPos = clampPosition(nextPosition, extent)
+47
View File
@@ -0,0 +1,47 @@
export enum ErrorCode {
MISSING_VIEWPORT_DIMENSIONS = 'MISSING_VIEWPORT_DIMENSIONS',
NODE_NOT_FOUND = 'NODE_NOT_FOUND',
NODE_MISSING_PARENT = 'NODE_MISSING_PARENT',
NODE_TYPE_MISSING = 'NODE_TYPE_MISSING',
NODE_EXTENT_INVALID = 'NODE_EXTENT_INVALID',
EDGE_INVALID = 'EDGE_INVALID',
EDGE_NOT_FOUND = 'EDGE_NOT_FOUND',
EDGE_SOURCE_MISSING = 'EDGE_SOURCE_MISSING',
EDGE_TARGET_MISSING = 'EDGE_TARGET_MISSING',
EDGE_TYPE_MISSING = 'EDGE_TYPE_MISSING',
EDGE_SOURCE_TARGET_SAME = 'EDGE_SOURCE_TARGET_SAME',
EDGE_SOURCE_TARGET_MISSING = 'EDGE_SOURCE_TARGET_MISSING',
EDGE_ORPHANED = 'EDGE_ORPHANED',
}
const messages = {
[ErrorCode.MISSING_VIEWPORT_DIMENSIONS]: () => 'The Vue Flow parent container needs a width and a height to render the graph',
[ErrorCode.NODE_NOT_FOUND]: (id: string) => `Node not found\nNode: ${id}`,
[ErrorCode.NODE_MISSING_PARENT]: (id: string, parentId: string) => `Node is missing a parent\nNode: ${id}\nParent: ${parentId}`,
[ErrorCode.NODE_TYPE_MISSING]: (type: string) => `Node type is missing\nType: ${type}`,
[ErrorCode.NODE_EXTENT_INVALID]: (id: string) => `Only child nodes can use a parent extent\nNode: ${id}`,
[ErrorCode.EDGE_INVALID]: (id: string) => `An edge needs a source and a target\nEdge: ${id}`,
[ErrorCode.EDGE_SOURCE_MISSING]: (id: string, source: string) => `Edge source is missing\nEdge: ${id} \nSource: ${source}`,
[ErrorCode.EDGE_TARGET_MISSING]: (id: string, target: string) => `Edge target is missing\nEdge: ${id} \nTarget: ${target}`,
[ErrorCode.EDGE_TYPE_MISSING]: (type: string) => `Edge type is missing\nType: ${type}`,
[ErrorCode.EDGE_SOURCE_TARGET_SAME]: (id: string, source: string, target: string) =>
`Edge source and target are the same\nEdge: ${id} \nSource: ${source} \nTarget: ${target}`,
[ErrorCode.EDGE_SOURCE_TARGET_MISSING]: (id: string, source: string, target: string) =>
`Edge source or target is missing\nEdge: ${id} \nSource: ${source} \nTarget: ${target}`,
[ErrorCode.EDGE_ORPHANED]: (id: string) =>
`Edge was orphaned (suddenly missing source or target) and has been removed\nEdge: ${id}`,
[ErrorCode.EDGE_NOT_FOUND]: (id: string) => `Edge not found\nEdge: ${id}`,
} as const
export class VueFlowError<T extends ErrorCode = ErrorCode> extends Error {
code: T
constructor(
code: T,
...args: (typeof messages)[T] extends (...args: any[]) => string ? Parameters<(typeof messages)[T]> : never
) {
// @ts-expect-error - TS doesn't know that the message is a key of messages
super(messages[code]?.(...args))
this.code = code
}
}
-6
View File
@@ -1,11 +1,5 @@
const productionEnvs = ['production', 'prod']
export class VueFlowError extends Error {
constructor(message: string, scope?: string) {
super(`[Vue Flow]: ${scope ? `${scope} - ` : ''}${message}`)
}
}
export function warn(message: string, ...args: any[]) {
if (!productionEnvs.includes(__ENV__ || '')) {
console.warn(`[Vue Flow]: ${message}`, ...args)
+13 -7
View File
@@ -1,10 +1,10 @@
import type { Actions, Connection, Edge, GraphEdge, GraphNode, Node } from '~/types'
import type { Actions, Connection, Edge, GraphEdge, GraphNode, Node, State } from '~/types'
export const isDef = <T>(val: T): val is NonNullable<T> => typeof unref(val) !== 'undefined'
export function addEdgeToStore(edgeParams: Edge | Connection, edges: Edge[]) {
export function addEdgeToStore(edgeParams: Edge | Connection, edges: Edge[], onError: State['hooks']['error']['trigger']) {
if (!edgeParams.source || !edgeParams.target) {
warn("Can't create edge. An edge needs a source and a target.")
onError(new VueFlowError(ErrorCode.EDGE_INVALID, (edgeParams as Edge).id))
return false
}
@@ -31,16 +31,17 @@ export function updateEdgeAction(
edges: GraphEdge[],
findEdge: Actions['findEdge'],
shouldReplaceId: boolean,
onError: State['hooks']['error']['trigger'],
) {
if (!newConnection.source || !newConnection.target) {
warn("Can't create new edge. An edge needs a source and a target.")
onError(new VueFlowError(ErrorCode.EDGE_INVALID, edge.id))
return false
}
const foundEdge = findEdge(edge.id)
if (!foundEdge) {
warn(`The old edge with id=${edge.id} does not exist.`)
onError(new VueFlowError(ErrorCode.EDGE_NOT_FOUND, edge.id))
return false
}
@@ -60,7 +61,12 @@ export function updateEdgeAction(
return newEdge
}
export function createGraphNodes(nodes: Node[], findNode: Actions['findNode'], currGraphNodes: GraphNode[]) {
export function createGraphNodes(
nodes: Node[],
currGraphNodes: GraphNode[],
findNode: Actions['findNode'],
onError: State['hooks']['error']['trigger'],
) {
const parentNodes: Record<string, true> = {}
const graphNodes = nodes.map((node) => {
@@ -82,7 +88,7 @@ export function createGraphNodes(nodes: Node[], findNode: Actions['findNode'], c
const parentNode = nextNodes.find((n) => n.id === node.parentNode)
if (node.parentNode && !parentNode) {
warn(`Parent node ${node.parentNode} not found`)
onError(new VueFlowError(ErrorCode.NODE_MISSING_PARENT, node.id, node.parentNode))
}
if (node.parentNode || parentNodes[node.id]) {