From 4319e2ac4987734ab99d3e337e69149b624cf285 Mon Sep 17 00:00:00 2001 From: Braks <78412429+bcakmakoglu@users.noreply.github.com> Date: Fri, 9 Jul 2021 17:37:09 +0200 Subject: [PATCH] feat: Implement MiniMap, Background and Controls components --- src/Basic.tsx | 5 + .../Background/index.tsx | 76 +++++++++ .../Background/utils.tsx | 7 + src/additional-components/Controls/index.tsx | 149 ++++++++++++++++++ .../MiniMap/MiniMapNode.tsx | 86 ++++++++++ src/additional-components/MiniMap/index.tsx | 131 +++++++++++++++ src/additional-components/index.ts | 6 + src/store/configure-store.ts | 1 - src/utils/graph.ts | 2 - 9 files changed, 460 insertions(+), 3 deletions(-) create mode 100644 src/additional-components/Background/index.tsx create mode 100644 src/additional-components/Background/utils.tsx create mode 100644 src/additional-components/Controls/index.tsx create mode 100644 src/additional-components/MiniMap/MiniMapNode.tsx create mode 100644 src/additional-components/MiniMap/index.tsx create mode 100644 src/additional-components/index.ts diff --git a/src/Basic.tsx b/src/Basic.tsx index 3c4af491..317cbd33 100644 --- a/src/Basic.tsx +++ b/src/Basic.tsx @@ -1,6 +1,7 @@ import { Connection, Edge, Elements, FlowElement, Node, OnLoadParams } from './types'; import { addEdge, isNode, removeElements } from './utils/graph'; import RevueFlow from './container/RevueFlow'; +import { MiniMap, Controls, Background } from './additional-components'; import { defineComponent, ref } from 'vue'; const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); @@ -16,6 +17,7 @@ const initialElements: Elements = [ ]; const BasicFlow = defineComponent({ + components: { RevueFlow, MiniMap, Controls, Background }, setup() { const elements = ref(initialElements); const rfInstance = ref(null); @@ -62,6 +64,9 @@ const BasicFlow = defineComponent({ minZoom={0.2} maxZoom={4} > + + +
+ ); + } +}); + +const Controls = defineComponent({ + name: 'Controls', + props: { + showZoom: { + type: Boolean as PropType, + required: false, + default: true + }, + showFitView: { + type: Boolean as PropType, + required: false, + default: true + }, + showInteractive: { + type: Boolean as PropType, + required: false, + default: true + }, + fitViewParams: { + type: Object as PropType, + required: false, + default: undefined + }, + onZoomIn: { + type: Function() as PropType, + required: false, + default: undefined + }, + onZoomOut: { + type: Function() as PropType, + required: false, + default: undefined + }, + onFitView: { + type: Function() as PropType, + required: false, + default: undefined + }, + onInteractiveChange: { + type: Function() as PropType, + required: false, + default: undefined + } + }, + setup(props, { slots }) { + const pinia = store(); + const isVisible = ref(false); + const { zoomIn, zoomOut, fitView } = useZoomPanHelper(); + + const isInteractive = pinia.nodesDraggable && pinia.nodesConnectable && pinia.elementsSelectable; + const mapClasses = ['react-flow__controls']; + + const onZoomInHandler = () => { + zoomIn?.(); + props.onZoomIn?.(); + }; + + const onZoomOutHandler = () => { + zoomOut?.(); + props.onZoomOut?.(); + }; + + const onFitViewHandler = () => { + fitView?.(props.fitViewParams); + props.onFitView?.(); + }; + + const onInteractiveChangeHandler = () => { + pinia.setInteractive?.(!isInteractive); + props.onInteractiveChange?.(!isInteractive); + }; + + onMounted(() => { + isVisible.value = true; + }); + + return () => ( +
+ {props.showZoom && ( + <> + + Plus + + + Minus + + + )} + {props.showFitView && ( + + FitView + + )} + {props.showInteractive && ( + + {isInteractive ? ( + Unlock + ) : ( + Lock + )} + + )} + {slots.default ? slots.default() : ''} +
+ ); + } +}); + +export default Controls; diff --git a/src/additional-components/MiniMap/MiniMapNode.tsx b/src/additional-components/MiniMap/MiniMapNode.tsx new file mode 100644 index 00000000..9399aa9f --- /dev/null +++ b/src/additional-components/MiniMap/MiniMapNode.tsx @@ -0,0 +1,86 @@ +import { defineComponent, PropType } from 'vue'; + +interface MiniMapNodeProps { + x: number; + y: number; + width: number; + height: number; + borderRadius: number; + color: string; + shapeRendering: string; + strokeColor: string; + strokeWidth: number; +} + +const MiniMapNode = defineComponent({ + name: 'MiniMapNode', + props: { + x: { + type: Number as PropType, + required: false, + default: undefined + }, + y: { + type: Number as PropType, + required: false, + default: undefined + }, + width: { + type: Number as PropType, + required: false, + default: undefined + }, + height: { + type: Number as PropType, + required: false, + default: undefined + }, + borderRadius: { + type: Number as PropType, + required: false, + default: undefined + }, + color: { + type: String as PropType, + required: false, + default: undefined + }, + shapeRendering: { + type: String as PropType, + required: false, + default: undefined + }, + strokeColor: { + type: String as PropType, + required: false, + default: undefined + }, + strokeWidth: { + type: Number as PropType, + required: false, + default: undefined + } + }, + setup(props, { attrs }: { attrs: Record }) { + const { background, backgroundColor } = attrs.style || {}; + const fill = (props.color || background || backgroundColor) as string; + + return () => ( + + ); + } +}); + +export default MiniMapNode; diff --git a/src/additional-components/MiniMap/index.tsx b/src/additional-components/MiniMap/index.tsx new file mode 100644 index 00000000..3d4e2759 --- /dev/null +++ b/src/additional-components/MiniMap/index.tsx @@ -0,0 +1,131 @@ +import { getRectOfNodes, getBoundsofRects } from '../../utils/graph'; +import { Node, Rect } from '../../types'; +import MiniMapNode from './MiniMapNode'; +import { computed, defineComponent, HTMLAttributes, PropType } from 'vue'; +import store from '../../store'; + +type StringFunc = (node: Node) => string; + +export interface MiniMapProps extends HTMLAttributes { + nodeColor?: string | StringFunc; + nodeStrokeColor?: string | StringFunc; + nodeClassName?: string | StringFunc; + nodeBorderRadius?: number; + nodeStrokeWidth?: number; + maskColor?: string; +} + +declare const window: any; + +const defaultWidth = 200; +const defaultHeight = 150; + +const MiniMap = defineComponent({ + name: 'MiniMap', + props: { + nodeStrokeColor: { + type: (String || Function) as PropType, + required: false, + default: '#555' + }, + nodeColor: { + type: (String || Function) as PropType, + required: false, + default: '#fff' + }, + nodeClassName: { + type: (String || Function) as PropType, + required: false, + default: '' + }, + nodeBorderRadius: { + type: Number as PropType, + required: false, + default: 5 + }, + nodeStrokeWidth: { + type: Number as PropType, + required: false, + default: 2 + }, + maskColor: { + type: String as PropType, + required: false, + default: 'rgb(240, 242, 243, 0.7)' + } + }, + setup(props, { attrs }: { attrs: Record }) { + const pinia = store(); + const transform = computed(() => pinia.transform); + + const mapClasses = ['react-flow__minimap']; + const elementWidth = computed(() => (attrs.style?.width || defaultWidth)! as number); + const elementHeight = computed(() => (attrs.style?.height || defaultHeight)! as number); + const nodeColorFunc = (props.nodeColor instanceof Function ? props.nodeColor : () => props.nodeColor) as StringFunc; + const nodeStrokeColorFunc = ( + props.nodeStrokeColor instanceof Function ? props.nodeStrokeColor : () => props.nodeStrokeColor + ) as StringFunc; + const nodeClassNameFunc = ( + props.nodeClassName instanceof Function ? props.nodeClassName : () => props.nodeClassName + ) as StringFunc; + const hasNodes = computed(() => pinia.nodes && pinia.nodes.length); + const bb = computed(() => getRectOfNodes(pinia.nodes)); + const viewBB = computed(() => ({ + x: -transform.value[0] / transform.value[2], + y: -transform.value[1] / transform.value[2], + width: pinia.width / transform.value[2], + height: pinia.height / transform.value[2] + })); + const boundingRect = computed(() => (hasNodes.value ? getBoundsofRects(bb.value, viewBB.value) : viewBB.value)); + const scaledWidth = computed(() => boundingRect.value.width / elementWidth.value); + const scaledHeight = computed(() => boundingRect.value.height / elementHeight.value); + const viewScale = computed(() => Math.max(scaledWidth.value, scaledHeight.value)); + const viewWidth = computed(() => viewScale.value * elementWidth.value); + const viewHeight = computed(() => viewScale.value * elementHeight.value); + const offset = computed(() => 5 * viewScale.value); + const x = computed(() => boundingRect.value.x - (viewWidth.value - boundingRect.value.width) / 2 - offset.value); + const y = computed(() => boundingRect.value.y - (viewHeight.value - boundingRect.value.height) / 2 - offset.value); + const width = computed(() => viewWidth.value + offset.value * 2); + const height = computed(() => viewHeight.value + offset.value * 2); + const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'; + + return () => ( + + {pinia.nodes + .filter((node) => !node.isHidden) + .map((node) => ( + + ))} + + + ); + } +}); + +export default MiniMap; diff --git a/src/additional-components/index.ts b/src/additional-components/index.ts new file mode 100644 index 00000000..2d79fd8c --- /dev/null +++ b/src/additional-components/index.ts @@ -0,0 +1,6 @@ +// These components are not used by React Flow directly +// but the user can add them as children of a React Flow component + +export { default as MiniMap } from './MiniMap'; +export { default as Controls, ControlButton } from './Controls'; +export { default as Background } from './Background'; diff --git a/src/store/configure-store.ts b/src/store/configure-store.ts index 814e6284..0cbde1df 100644 --- a/src/store/configure-store.ts +++ b/src/store/configure-store.ts @@ -31,7 +31,6 @@ export default function configureStore( this.onConnectStop = onConnectStop; }, setElements(elements) { - console.log('setting elements'); const propElements = elements; const nextElements: NextElements = { nextNodes: [], diff --git a/src/utils/graph.ts b/src/utils/graph.ts index 179a6b44..ff571568 100644 --- a/src/utils/graph.ts +++ b/src/utils/graph.ts @@ -88,8 +88,6 @@ export const addEdge = (edgeParams: Edge | Connection, elements: Elements): Elem return elements; } - console.log(edge); - return elements.concat(edge); };