feat(core): add onError hook
Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com>
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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]) {
|
||||
|
||||
Reference in New Issue
Block a user