docs: use vitepress

This commit is contained in:
braks
2022-08-01 21:10:14 +02:00
committed by Braks
parent bd36253cd0
commit e6bef4a9f1
140 changed files with 8166 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
<script lang="ts" setup>
import Heart from '~icons/mdi/heart'
</script>
<template>
<div id="acknowledgement" class="w-full dark:(bg-black text-white) border-y-1 border-white">
<div class="max-w-11/12 md:max-w-6/12 m-auto py-4 md:(pb-12 pt-6) text-center">
<div>
<h1 class="md:mb-8 flex justify-center items-center"><Heart class="text-red-500" /> Acknowledgement</h1>
<strong>First off</strong>: A big thank you to the developers of
<a href="https://webkid.io" target="_blank" class="text-green-500">Webkid</a>. Their amazing work made it possible for me
to create this port to Vue 3 - without them there is no Vue Flow.
</div>
<br />
<div>
Vue Flow is built on top of existing features and code taken from
<a href="https://reactflow.dev" target="_blank" class="text-green-500">React Flow</a>. It replicates the basic features
found in React Flow (zoom, pan, graph, additional components and more) and brings them to the Vue 3 experience, with all
the fun reactivity and features like template slots etc. you know and love from Vue. If you're happy with Vue Flow,
<br />
<a href="https://github.com/sponsors/wbkd" target="_blank" class="text-lg font-bold text-green-500">
please consider supporting Webkid by donating.
</a>
</div>
</div>
</div>
</template>
+31
View File
@@ -0,0 +1,31 @@
<script lang="ts" setup>
import { $fetch } from 'ohmyfetch'
import Star from '~icons/carbon/star'
import Download from '~icons/carbon/download'
const githubData = await $fetch('https://api.github.com/repos/bcakmakoglu/vue-flow?page=$i&per_page=100')
const npmData = await $fetch('https://api.npmjs.org/downloads/point/last-month/@braks/vue-flow')
</script>
<template>
<div class="w-full dark:(bg-black text-white border-white) border-black border-y-1">
<div class="max-w-full md:max-w-11/12 m-auto py-4 md:py-12 <md:(dark:border-t-1 border-white)">
<div class="grid md:grid-cols-3 gap-3 text-center <md:divide-y md:divide-x dark:divide-white divide-black">
<div class="grid grid-rows-auto gap-2 py-4 md:py-0">
<div class="text-gray-400 font-semibold text-lg">Stargazers</div>
<div class="font-bold text-3xl flex gap-2 items-center justify-center"><Star /> {{ githubData.stargazers_count }}</div>
</div>
<div class="grid grid-rows-2 gap-2 py-4 md:py-0">
<div class="text-gray-400 font-semibold text-lg">Downloads (last month)</div>
<div class="font-bold text-3xl flex gap-2 items-center justify-center"><Download /> {{ npmData.downloads }}</div>
</div>
<div class="grid grid-rows-2 gap-2 py-4 md:py-0">
<div class="text-gray-400 font-semibold text-lg">License</div>
<div class="font-bold text-3xl">
{{ githubData.license.key.toUpperCase() }}
</div>
</div>
</div>
</div>
</div>
</template>
+56
View File
@@ -0,0 +1,56 @@
<script lang="ts" setup>
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
import type { VueFlowStore } from '@braks/vue-flow'
const breakpoints = useBreakpoints(breakpointsTailwind)
const el = ref()
const instances: VueFlowStore[] = []
const onLoad = (instance: VueFlowStore) => {
instances.push(instance)
instance.fitView()
}
const fitViews = () => {
instances.forEach((i) => i.fitView())
}
const { stop } = useResizeObserver(
el,
useDebounceFn(() => fitViews(), 5),
)
onBeforeUnmount(stop)
</script>
<template>
<div ref="el" class="w-full dark:(bg-black text-white)">
<div
class="flex flex-col divide-y divide-gray-500 md:divide-y-0 gap-12 md:gap-24 lg:gap-36 max-w-9/12 md:max-w-11/12 lg:max-w-9/12 m-auto py-12 md:py-24 text-center md:text-left"
>
<XyzTransition appear-visible xyz="fade down ease-out-back">
<div class="flex flex-col md:flex-row gap-12 md:gap-24">
<Basic @pane="onLoad" />
</div>
</XyzTransition>
<XyzTransition appear-visible xyz="fade down ease-out-back">
<div class="flex flex-col-reverse md:flex-row flex-unwrap gap-12 md:gap-24">
<RGB @pane="onLoad" />
</div>
</XyzTransition>
<XyzTransition appear-visible xyz="fade down ease-out-back">
<div class="flex flex-col md:flex-row flex-unwrap gap-12 md:gap-24">
<Nested @pane="onLoad" />
</div>
</XyzTransition>
<XyzTransition appear-visible xyz="fade down ease-out-back">
<div class="flex flex-col-reverse md:flex-row flex-unwrap gap-12 md:gap-24">
<Additional @pane="onLoad" />
</div>
</XyzTransition>
</div>
</div>
</template>
+83
View File
@@ -0,0 +1,83 @@
<script lang="ts" setup>
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
import Blobity from 'blobity'
import Intro from './flows/Intro.vue'
const breakpoints = useBreakpoints(breakpointsTailwind)
const usesDark = useDark({
storageKey: 'vuepress-color-scheme',
selector: 'html',
})
const dark = ref(false)
onMounted(() => {
const html = document.getElementsByTagName('html')![0]
usesDark.value = html.classList.contains('dark')
const observer = new MutationObserver((mutations) => {
for (const m of mutations) {
dark.value = html.classList.contains('dark')
}
})
observer.observe(html, {
attributes: true,
attributeOldValue: true,
attributeFilter: ['class'],
})
})
onMounted(() => {
if (!breakpoints.isSmaller('md')) {
const blobity = new Blobity({
color: dark.value ? '#ffffff' : '#000000',
invert: true,
zIndex: 0,
magnetic: false,
dotColor: '#10b981',
radius: 8,
focusableElementsOffsetX: 5,
focusableElementsOffsetY: 4,
mode: 'normal',
focusableElements:
'[data-blobity], a:not([data-no-blobity]), button:not([data-no-blobity]), [data-blobity-tooltip], .back-to-top, .intro',
})
onBeforeUnmount(() => {
blobity.destroy()
})
}
})
</script>
<template>
<div class="relative h-[90vh] md:h-[75vh]">
<Intro />
</div>
</template>
<style>
button:focus {
outline: none;
}
h1 {
@apply text-xl lg:text-4xl mb-4 font-bold;
}
h2 {
@apply text-lg lg:text-2xl mb-4 font-semibold;
}
p {
@apply text-md lg:text-lg;
}
p ~ h1,
p ~ h2 {
@apply mt-6;
}
</style>
+57
View File
@@ -0,0 +1,57 @@
<script lang="ts" setup>
import { computed } from 'vue'
import type { EdgeProps, MarkerType, Position } from '@braks/vue-flow'
import { getBezierPath } from '@braks/vue-flow'
interface CustomEdgeProps extends EdgeProps {
source: string
target: string
sourceHandleId?: string
targetHandleId?: string
id: string
sourceX: number
sourceY: number
targetX: number
targetY: number
sourcePosition: Position
targetPosition: Position
markerEnd?: MarkerType
data?: {
text?: string
color?: 'red' | 'green' | 'blue'
}
}
const props = defineProps<CustomEdgeProps>()
const edgePath = computed(() =>
getBezierPath({
sourceX: props.sourceX,
sourceY: props.sourceY,
sourcePosition: props.sourcePosition,
targetX: props.targetX,
targetY: props.targetY,
targetPosition: props.targetPosition,
}),
)
</script>
<script lang="ts">
export default {
inheritAttrs: false,
}
</script>
<template>
<path
:id="props.id"
class="vue-flow__edge-path"
:style="{ stroke: props.data?.color }"
:d="edgePath"
:marker-end="props.markerEnd"
/>
<text>
<textPath :href="`#${props.id}`" :style="{ fontSize: '1.25rem', fill: 'white' }" startOffset="50%" text-anchor="middle">
{{ props.data?.text }}
</textPath>
</text>
</template>
+112
View File
@@ -0,0 +1,112 @@
<script lang="ts" setup>
import { ref } from 'vue'
import type { Elements } from '@braks/vue-flow'
import { Background, Controls, MiniMap, Position, VueFlow, useVueFlow } from '@braks/vue-flow'
const emit = defineEmits(['pane'])
const elements = ref<Elements>([
{
id: '1',
style: { width: '75px' },
type: 'input',
sourcePosition: Position.Right,
label: 'input',
position: { x: 25, y: 120 },
},
{
id: '2',
label: 'A',
targetPosition: Position.Left,
sourcePosition: Position.Right,
position: { x: 150, y: 25 },
style: { width: '75px' },
},
{
id: '3',
label: 'B',
targetPosition: Position.Left,
sourcePosition: Position.Right,
position: { x: 250, y: 25 },
style: { width: '75px' },
},
{
id: '4',
label: 'C',
targetPosition: Position.Left,
sourcePosition: Position.Right,
position: { x: 350, y: 25 },
style: { width: '75px' },
},
{
id: '5',
label: 'D',
targetPosition: Position.Left,
sourcePosition: Position.Right,
position: { x: 150, y: 220 },
style: { width: '75px' },
},
{
id: '6',
label: 'E',
targetPosition: Position.Left,
sourcePosition: Position.Right,
position: { x: 250, y: 220 },
style: { width: '75px' },
},
{
id: '7',
label: 'F',
targetPosition: Position.Left,
sourcePosition: Position.Right,
position: { x: 350, y: 220 },
style: { width: '75px' },
},
{
id: '8',
type: 'output',
label: 'Output',
targetPosition: Position.Left,
position: { x: 500, y: 120 },
style: { width: '75px' },
},
{ id: 'e1-2', type: 'step', source: '1', target: '2' },
{ id: 'e2-3', type: 'step', source: '2', target: '3' },
{ id: 'e3-4', type: 'step', source: '3', target: '4' },
{ id: 'e4-8', type: 'step', source: '4', target: '8' },
{ id: 'e1-5', type: 'step', source: '1', target: '5', animated: true },
{ id: 'e5-6', type: 'step', source: '5', target: '6', animated: true },
{ id: 'e6-7', type: 'step', source: '6', target: '7', animated: true },
{ id: 'e6-8', type: 'step', source: '7', target: '8', animated: true },
])
const { onPaneReady } = useVueFlow({
modelValue: elements.value,
zoomOnScroll: false,
panOnDrag: false,
})
onPaneReady((i) => emit('pane', i))
</script>
<template>
<div class="w-full h-[300px] md:min-h-[400px] shadow-xl rounded-xl font-mono uppercase overflow-hidden border-2">
<VueFlow>
<Controls :show-interactive="false" />
<MiniMap mask-color="rgba(16, 185, 129, 0.5)" class="transform scale-60 origin-bottom-right opacity-75" />
<Background variant="lines" pattern-color="#aaa" :gap="46" />
</VueFlow>
</div>
<div class="md:max-w-1/3 flex flex-col gap-12 justify-center <md:pt-12">
<div class="flex flex-col gap-2 items-center md:items-start">
<h1>Additional Features</h1>
<p>
On top of all the features Vue Flow comes with several components like a Background, Minimap or Controls.
<br />
Plus it's built for composition, making the access of the internal state easy as can be!
</p>
<router-link class="docs-button max-w-max" to="/guide/"> Documentation </router-link>
</div>
</div>
</template>
+143
View File
@@ -0,0 +1,143 @@
<script lang="ts" setup>
import type { ClassFunc, GraphEdge, GraphNode, StyleFunc } from '@braks/vue-flow'
import { Background, ConnectionLineType, Controls, VueFlow, useVueFlow } from '@braks/vue-flow'
import Cross from '~icons/mdi/window-close'
const emit = defineEmits(['pane'])
const getNodeClass: ClassFunc<GraphNode> = (el) => {
const classes = ['font-semibold', '!border-2', 'transition-colors', 'duration-300', 'ease-in-out']
if (el.selected)
classes.push(
...['!border-green-500/80', '!shadow-md', '!shadow-green-500/50', '!bg-green-100/80 dark:(!bg-white)', '!text-gray-700'],
)
return classes.join(' ')
}
const getEdgeClass: ClassFunc<GraphEdge> = (el) => {
const classes = ['transition-colors duration-300', el.sourceNode.selected ? 'font-semibold' : '']
return classes.join(' ')
}
const getEdgeStyle: StyleFunc<GraphEdge> = (el) => {
const sourceNodeSelected = el.sourceNode.selected
return {
transition: 'stroke ease-in-out 300ms',
stroke: el.selected || sourceNodeSelected ? 'var(--secondary)' : '',
}
}
const { onPaneReady, onConnect, addEdges, viewport } = useVueFlow({
connectionLineType: ConnectionLineType.SmoothStep,
connectionLineStyle: {
strokeDasharray: 5,
animation: 'dashdraw 0.5s linear infinite',
},
modelValue: [
{
id: '1',
type: 'input',
label: 'Start',
position: { x: 250, y: 5 },
class: getNodeClass,
},
{
id: '2',
label: 'Waypoint',
position: { x: 100, y: 100 },
class: getNodeClass,
},
{ id: '3', label: 'Waypoint', position: { x: 400, y: 100 }, class: getNodeClass },
{
id: '4',
type: 'output',
label: 'End',
position: { x: 250, y: 225 },
class: getNodeClass,
},
{
id: 'e1-2',
source: '1',
label: 'animated edge',
target: '2',
animated: true,
class: getEdgeClass,
style: getEdgeStyle,
},
{
id: 'e1-3',
source: '1',
target: '3',
label: 'default edge',
class: getEdgeClass,
style: (el: GraphEdge) => {
const sourceNodeSelected = el.sourceNode.selected
return {
transition: 'stroke ease-in-out 300ms',
stroke: el.selected || sourceNodeSelected ? 'red' : '',
}
},
},
{
id: 'e2-4',
source: '2',
target: '4',
type: 'step',
animated: true,
class: getEdgeClass,
style: getEdgeStyle,
},
],
})
onPaneReady((i) => emit('pane', i))
onConnect((param) => {
addEdges([
{
...param,
type: 'smoothstep',
animated: true,
},
])
})
</script>
<template>
<div class="md:max-w-1/3 flex flex-col justify-center">
<div class="flex flex-col items-center md:items-start">
<h1>Interactive Graphs</h1>
<p>
Vue Flow comes with built-in features like zoom & pan and dedicated controls, single & multi-selections, draggable
elements, customizable nodes and edges and a bunch of event handlers.
</p>
<router-link class="docs-button max-w-max" to="/guide/"> Documentation </router-link>
</div>
</div>
<div
class="w-full h-[300px] md:min-h-[400px] shadow-xl rounded-xl font-mono uppercase border-1 border-secondary overflow-hidden"
>
<VueFlow class="basic">
<Controls class="md:(!left-auto !right-[10px])" />
<Background :gap="60">
<template #pattern>
<Cross :style="{ fontSize: `${8 * viewport.zoom || 1}px` }" class="text-[#10b981] opacity-50" />
</template>
</Background>
</VueFlow>
</div>
</template>
<style>
.basic .vue-flow__node-input.selected .vue-flow__handle {
@apply bg-green-500;
}
.basic .vue-flow__node-default.selected .vue-flow__handle {
@apply bg-green-500;
}
.basic .vue-flow__node-output.selected .vue-flow__handle {
@apply bg-green-500;
}
</style>
+451
View File
@@ -0,0 +1,451 @@
<script lang="ts" setup>
import { Background, Handle, Position, VueFlow, useVueFlow } from '@braks/vue-flow'
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
import confetti from 'canvas-confetti'
import colors from 'windicss/colors'
import { cheer, fireworks } from './confetti'
import Heart from '~icons/mdi/heart'
const breakpoints = useBreakpoints(breakpointsTailwind)
const dark = ref(false)
const animatedBackground = ref(false)
onMounted(() => {
const html = document.getElementsByTagName('html')![0]
const observer = new MutationObserver((mutations) => {
for (const m of mutations) {
dark.value = html.classList.contains('dark')
}
})
observer.observe(html, {
attributes: true,
attributeOldValue: true,
attributeFilter: ['class'],
})
})
const initialEdges = [
{
id: 'eintro-examples',
sourceHandle: 'a',
source: 'intro',
target: 'examples',
animated: true,
style: { strokeWidth: 4, stroke: '#ef467e' },
},
{
id: 'eintro-documentation',
sourceHandle: 'a',
source: 'intro',
target: 'documentation',
animated: true,
style: { strokeWidth: 4, stroke: '#f97316' },
},
{
id: 'eintro-acknowledgement',
sourceHandle: 'a',
source: 'intro',
target: 'acknowledgement',
animated: true,
style: { strokeWidth: 4, stroke: '#0ea5e9' },
},
]
const { dimensions, onNodeClick, getNodes, fitView, getNode, getEdge, updateEdge, edges, setEdges } = useVueFlow({
nodes: [
{ id: 'intro', type: 'box', position: { x: 0, y: 0 } },
{ id: 'examples', type: 'box', position: { x: -50, y: 400 } },
{ id: 'documentation', type: 'box', position: { x: 300, y: 400 } },
{ id: 'acknowledgement', type: 'box', position: { x: 150, y: 500 } },
],
edges: initialEdges,
elementsSelectable: true,
panOnDrag: false,
zoomOnScroll: false,
zoomOnDoubleClick: false,
zoomOnPinch: false,
elevateEdgesOnSelect: true,
})
const clickInterval = ref()
const clicks = ref(0)
const disabled = ref(false)
const confettiColors = Object.values(colors).flatMap((color) => {
if (typeof color === 'string') return color
else return Object.values(color).flatMap((c) => c)
})
onNodeClick(async ({ node }) => {
if (node.id === 'intro') {
if (disabled.value) return
animatedBackground.value = !animatedBackground.value
if (clickInterval.value) {
clearInterval(clickInterval.value)
}
clickInterval.value = setInterval(() => {
clearInterval(clickInterval.value)
clicks.value = 0
}, 1000)
clicks.value++
if (clicks.value < 10) {
confetti({
startVelocity: 25,
spread: 360,
angle: 270,
origin: { y: 0.1 },
colors: confettiColors,
})
} else if (clicks.value < 20) {
await cheer(['#10b981', dark.value ? '#ffffff' : '#000000'])
} else if (clicks.value === 20) {
disabled.value = true
await fireworks(confettiColors)
disabled.value = false
} else {
clicks.value = 0
}
}
})
const el = templateRef<HTMLDivElement>('el', null)
const setNodes = () => {
if (breakpoints.isSmaller('md')) {
const mainNode = getNode.value('intro')!
getNodes.value.forEach((node) => {
switch (node.id) {
case 'intro':
node.position = { x: 0, y: 0 }
break
case 'examples':
node.position = {
x: mainNode.dimensions.width / 2 - node.dimensions.width / 2,
y: mainNode.dimensions.height * 1.5,
}
break
case 'documentation':
node.position = {
x: mainNode.dimensions.width / 2 - node.dimensions.width / 2,
y: mainNode.dimensions.height * 2 + 50,
}
break
case 'acknowledgement':
node.position = {
x: mainNode.dimensions.width / 2 - node.dimensions.width / 2,
y: mainNode.dimensions.height * 3,
}
break
}
})
setEdges(() => {
return [
{
id: 'eintro-examples',
sourceHandle: 'a',
source: 'intro',
target: 'examples',
animated: true,
style: { strokeWidth: 4, stroke: '#ef467e' },
},
{
id: 'eexamples-documentation',
source: 'examples',
target: 'documentation',
animated: true,
style: { strokeWidth: 4, stroke: '#f97316' },
},
{
id: 'edocumentation-acknowledgement',
source: 'documentation',
target: 'acknowledgement',
animated: true,
style: { strokeWidth: 4, stroke: '#0ea5e9' },
},
]
})
} else {
getNodes.value.forEach((node) => {
const mainNode = getNode.value('intro')!
switch (node.id) {
case 'intro':
node.position = { x: 0, y: 0 }
break
case 'examples':
node.position = { x: -node.dimensions.width / 2, y: mainNode.dimensions.height * 1.5 }
break
case 'documentation':
node.position = {
x: mainNode.dimensions.width - node.dimensions.width / 2,
y: mainNode.dimensions.height * 1.5,
}
break
case 'acknowledgement':
node.position = {
x: mainNode.dimensions.width / 2 - node.dimensions.width / 2,
y: mainNode.dimensions.height * 2,
}
break
}
})
setEdges(initialEdges)
}
nextTick(() => {
fitView()
})
}
const { stop } = useResizeObserver(el, useDebounceFn(setNodes, 5))
onBeforeUnmount(stop)
const scrollTo = () => {
const el = document.getElementById('acknowledgement')
if (el) {
el.scrollIntoView({ behavior: 'smooth' })
}
}
const animationClassNames = ['checker-gb', 'checker-op', 'checker-yg', 'checker-ss']
const shuffle = (a: any[]) => {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[a[i], a[j]] = [a[j], a[i]]
}
return a
}
const createAnimationDurations = () => {
return animationClassNames.map((className) => {
const duration = 5 + Math.random() * 5
return {
className,
duration,
}
})
}
const animations = ref<{ className: string; duration: number }[]>(shuffle(createAnimationDurations()))
</script>
<template>
<VueFlow ref="el" class="dark:bg-black bg-white transition-colors duration-200 ease-in-out">
<XyzTransition xyz="fade down ease-out-back duration-10" mode="out-in">
<Background v-if="animatedBackground">
<template #pattern-container="{ id }">
<pattern :id="id" x="0" y="0" width="200" height="200" patternUnits="userSpaceOnUse">
<rect
:class="animations[0].className"
:style="{ '--animation-duration': `${animations[0].duration}s` }"
x="0"
y="0"
width="50"
height="50"
></rect>
<rect
:class="animations[1].className"
:style="{ '--animation-duration': `${animations[1].duration}s` }"
x="0"
y="100"
width="50"
height="50"
></rect>
<rect
:class="animations[2].className"
:style="{ '--animation-duration': `${animations[2].duration}s` }"
x="100"
y="0"
width="50"
height="50"
></rect>
<rect
:class="animations[3].className"
:style="{ '--animation-duration': `${animations[3].duration}s` }"
x="100"
y="100"
width="50"
height="50"
></rect>
</pattern>
</template>
</Background>
</XyzTransition>
<XyzTransition xyz="fade down ease-out-back duration-20" mode="out-in">
<Background
v-if="!animatedBackground"
variant="lines"
:pattern-color="dark ? '#ffffff' : '#000000'"
:size="0.7"
:gap="100"
/>
</XyzTransition>
<template #node-box="props">
<template v-if="props.id === 'intro'">
<div class="box max-w-[500px]">
<div class="intro px-4 py-2 shadow-lg rounded-md border-2 border-solid border-black">
<div class="font-mono flex flex-col gap-4 p-4 items-center text-center">
<h1 class="text-2xl lg:text-4xl !my-0 !pt-0 font-bold">Vue Flow</h1>
<h2 class="text-lg lg:text-xl font-normal !border-0">
The customizable Vue 3 component bringing interactivity to flowcharts and graphs.
</h2>
</div>
<Handle id="a" type="source" :position="Position.Bottom" />
</div>
</div>
</template>
<template v-else-if="props.id === 'documentation'">
<div class="flex">
<router-link class="link group bg-[#f15a16]" to="/guide/"> Read The Documentation </router-link>
</div>
<Handle type="target" :position="Position.Top" />
<Handle class="block md:hidden" type="source" :position="Position.Bottom" />
</template>
<template v-else-if="props.id === 'examples'">
<div class="flex">
<router-link class="link group bg-[#ef467e]" to="/examples/"> Check The Examples </router-link>
</div>
<Handle type="target" :position="Position.Top" />
<Handle class="block md:hidden" type="source" :position="Position.Bottom" />
</template>
<template v-else-if="props.id === 'acknowledgement'">
<div class="flex" @click="scrollTo">
<button class="link group bg-sky-500"><Heart class="text-red-500" /> Acknowledgement</button>
</div>
<Handle type="target" :position="Position.Top" />
</template>
</template>
</VueFlow>
</template>
<style>
.checker-gb {
animation: fill-green-blue var(--animation-duration) alternate infinite;
}
.checker-gb path {
animation: fill-green-blue var(--animation-duration) alternate infinite;
}
.checker-op {
animation: fill-orange-purple var(--animation-duration) alternate infinite;
}
.checker-op path {
animation: fill-orange-purple var(--animation-duration) alternate infinite;
}
.checker-yg {
animation: fill-yellow-green var(--animation-duration) alternate infinite;
}
.checker-yg path {
animation: fill-yellow-green var(--animation-duration) alternate infinite;
}
.checker-ss {
animation: fill-sky-red var(--animation-duration) alternate infinite;
}
.checker-ss path {
animation: fill-sky-red var(--animation-duration) alternate infinite;
}
@keyframes fill-green-blue {
0% {
@apply fill-transparent;
}
35% {
@apply fill-green-500/50;
}
65% {
@apply fill-transparent;
}
100% {
@apply fill-blue-500/50;
}
}
@keyframes fill-orange-purple {
0% {
@apply fill-transparent;
}
35% {
@apply fill-orange-500/50;
}
65% {
@apply fill-transparent;
}
100% {
@apply fill-purple-500/50;
}
}
@keyframes fill-yellow-green {
0% {
@apply fill-transparent;
}
35% {
@apply fill-yellow-500/50;
}
65% {
@apply fill-transparent;
}
100% {
@apply fill-green-500/50;
}
}
@keyframes fill-sky-red {
0% {
@apply fill-transparent;
}
35% {
@apply fill-sky-500/50;
}
65% {
@apply fill-transparent;
}
100% {
@apply fill-red-500/50;
}
}
.intro {
@apply cursor-pointer
bg-green-500
text-white
transform
transition-transform
duration-300
hover:(ring ring-white);
}
.link {
@apply flex
gap-3
items-center
p-4
shadow-lg
transform
transition-transform
duration-300
hover:(scale-102)
transition-colors
ease-in-out
rounded-lg
text-white
font-semibold
text-lg;
}
</style>
+85
View File
@@ -0,0 +1,85 @@
<script lang="ts" setup>
import { Background, ConnectionMode, Controls, VueFlow, useVueFlow } from '@braks/vue-flow'
import { breakpointsTailwind } from '@vueuse/core'
const emit = defineEmits(['pane'])
const breakpoints = useBreakpoints(breakpointsTailwind)
const nodeClasses = ['!normal-case font-semibold !text-white', '!border-1', 'shadow-md'].join(' ')
const childClasses = `${nodeClasses} !bg-green-500/70 !border-white`
const { onPaneReady, panOnDrag } = useVueFlow({
fitViewOnInit: true,
connectionMode: ConnectionMode.Loose,
zoomOnScroll: false,
preventScrolling: false,
translateExtent: [
[-500, -100],
[600, 500],
],
nodes: [
{ id: '1', type: 'input', label: 'Outer Node', position: { x: 0, y: 0 }, class: childClasses },
{
id: '2',
label: 'Parent Node',
position: { x: -125, y: 100 },
class: `${nodeClasses} !bg-green-500/30 !border-green-500`,
style: { width: '400px', height: '160px' },
},
{
id: '2a',
label: 'Child Node',
position: { x: 17, y: 30 },
parentNode: '2',
extent: 'parent',
class: childClasses,
},
{ id: '2b', label: 'Child Node', position: { x: 225, y: 30 }, parentNode: '2', extent: 'parent', class: childClasses },
{ id: '2c', label: 'Child Node', position: { x: 125, y: 100 }, parentNode: '2', extent: 'parent', class: childClasses },
{ id: '3', type: 'output', label: 'Outer Node', position: { x: 0, y: 300 }, class: childClasses },
],
edges: [
{ id: 'e1-2a', source: '1', target: '2a', type: 'smoothstep', style: { stroke: 'white', strokeWidth: '2' } },
{ id: 'e2-3', source: '2', target: '3', type: 'smoothstep', style: { stroke: 'white', strokeWidth: '2' } },
],
})
watch(
[breakpoints.sm, breakpoints.md, breakpoints.lg, breakpoints.xl, breakpoints['2xl']],
() => {
panOnDrag.value = !breakpoints.isSmaller('md')
},
{ immediate: true },
)
onPaneReady((i) => emit('pane', i))
</script>
<template>
<div class="md:max-w-1/3 flex flex-col justify-center <md:pt-12">
<div class="flex flex-col gap-2 items-center md:items-start">
<h1>Complex Flows</h1>
<p>
You want to visualize more complex ideas?
<br />
No worries! Vue Flow supports creating nested nodes and nested graphs out-of-the-box.
</p>
<router-link class="docs-button max-w-max" to="/guide/"> Documentation </router-link>
</div>
</div>
<div
class="w-full h-[300px] md:min-h-[400px] shadow-xl rounded-xl font-mono uppercase border-1 border-green-500 overflow-hidden"
>
<VueFlow class="nested">
<Controls class="md:(!left-auto !right-[10px])" />
<Background pattern-color="#aaa" class="!bg-gray-800" :gap="18" />
</VueFlow>
</div>
</template>
<style>
.nested .vue-flow__handle {
opacity: 0;
}
</style>
+114
View File
@@ -0,0 +1,114 @@
<script lang="ts" setup>
import type { MiniMapNodeFunc } from '@braks/vue-flow'
import { Background, BackgroundVariant, Controls, MiniMap, VueFlow, useVueFlow } from '@braks/vue-flow'
import { breakpointsTailwind } from '@vueuse/core'
import CustomEdge from '../edges/Custom.vue'
import RGBNode from '../nodes/Input.vue'
import RGBOutputNode from '../nodes/Output.vue'
const emit = defineEmits(['pane'])
const breakpoints = useBreakpoints(breakpointsTailwind)
interface Colors {
red: number
green: number
blue: number
}
const { onPaneReady, getNode, panOnDrag } = useVueFlow({
id: 'rgb-flow',
nodes: [
{ id: '1', type: 'rgb', data: { color: 'g' }, position: { x: -25, y: 0 } },
{ id: '2', type: 'rgb', data: { color: 'r' }, position: { x: 50, y: -110 } },
{ id: '3', type: 'rgb', data: { color: 'b' }, position: { x: 0, y: 110 } },
{ id: '4', type: 'rgb-output', label: 'RGB', position: { x: 400, y: -25 } },
],
edges: [
{ id: 'e1-4', type: 'rgb-line', data: { color: 'green' }, source: '1', target: '4', animated: true },
{ id: 'e2-4', type: 'rgb-line', data: { color: 'red' }, source: '2', target: '4', animated: true },
{ id: 'e3-4', type: 'rgb-line', data: { color: 'blue' }, source: '3', target: '4', animated: true },
],
zoomOnScroll: false,
nodeExtent: [
[-50, -150],
[500, 300],
],
})
onPaneReady((i) => emit('pane', i))
const el = templateRef<HTMLDivElement>('el', null)
const color = ref<Colors>({
red: 222,
green: 45,
blue: 140,
})
watch(
[breakpoints.sm, breakpoints.md, breakpoints.lg, breakpoints.xl, breakpoints['2xl']],
() => {
const mobile = breakpoints.isSmaller('md')
if (mobile) {
getNode.value('4')!.position = { x: 300, y: -25 }
}
panOnDrag.value = !mobile
},
{ immediate: true },
)
const onChange = ({ color: c, val }: { color: keyof Colors; val: number }) => (color.value[c] = Number(val))
const nodeColor: MiniMapNodeFunc = (node) => {
switch (node.id) {
case '1':
return 'green'
case '2':
return 'red'
case '3':
return 'blue'
case '4':
return `rgb(${color.value.red}, ${color.value.green}, ${color.value.blue})`
}
return ''
}
</script>
<template>
<div
ref="el"
class="w-full h-[300px] md:min-h-[400px] shadow-xl rounded-xl font-mono uppercase overflow-hidden bg-gray-800 border-2"
:style="{ borderColor: `rgb(${color.red}, ${color.green}, ${color.blue})` }"
>
<VueFlow class="relative font-mono">
<template #edge-rgb-line="rgbLineProps">
<CustomEdge v-bind="{ ...rgbLineProps, data: { text: color[rgbLineProps.data?.color], ...rgbLineProps.data } }" />
</template>
<template #node-rgb="rgbProps">
<RGBNode v-bind="rgbProps" :amount="color" @change="onChange" />
</template>
<template #node-rgb-output="rgbOutputProps">
<RGBOutputNode v-bind="rgbOutputProps" :rgb="`rgb(${color.red}, ${color.green}, ${color.blue})`" />
</template>
<Controls class="hidden md:block" />
<Background
:variant="BackgroundVariant.Lines"
:pattern-color="`rgb(${color.red}, ${color.green}, ${color.blue})`"
:gap="48"
:size="1"
/>
<MiniMap class="hidden sm:block transform scale-25 md:scale-50 lg:scale-75 origin-bottom-right" :node-color="nodeColor" />
</VueFlow>
</div>
<div class="md:max-w-1/3 flex flex-col gap-12 justify-center <md:pt-12">
<div class="flex flex-col gap-2 items-center md:items-start">
<h1>Customizable</h1>
<p>
You can expand on the existing features by using your own custom nodes and edges and implement any design and
functionality you want.
</p>
<router-link class="docs-button max-w-max" to="/guide/">Documentation</router-link>
</div>
</div>
</template>
+69
View File
@@ -0,0 +1,69 @@
import confetti from 'canvas-confetti'
export const fireworks = (colors?: string[]) => {
return new Promise((resolve) => {
const duration = 15 * 1000
const animationEnd = Date.now() + duration
const defaults = { startVelocity: 60, spread: 360, ticks: 20, zIndex: 0 }
function randomInRange(min: number, max: number) {
return Math.random() * (max - min) + min
}
const interval: any = setInterval(function () {
const timeLeft = animationEnd - Date.now()
if (timeLeft <= 0) {
clearInterval(interval)
return resolve(true)
}
const particleCount = 50 * (timeLeft / duration)
// since particles fall down, start a bit higher than random
confetti(
Object.assign({}, defaults, {
particleCount,
origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 },
colors,
}),
)
confetti(
Object.assign({}, defaults, {
particleCount,
origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 },
colors,
}),
)
}, 250)
})
}
export const cheer = (colors: string[]) => {
return new Promise((resolve) => {
const end = Date.now() + 2 * 1000
function frame() {
confetti({
particleCount: 2,
angle: 60,
spread: 75,
origin: { x: 0 },
colors,
})
confetti({
particleCount: 2,
angle: 120,
spread: 75,
origin: { x: 1 },
colors,
})
if (Date.now() < end) {
requestAnimationFrame(frame)
} else {
resolve(true)
}
}
frame()
})
}
+46
View File
@@ -0,0 +1,46 @@
<script lang="ts" setup>
import type { NodeProps } from '@braks/vue-flow'
import { Handle, Position } from '@braks/vue-flow'
interface RGBNodeProps extends NodeProps {
data: {
color: 'r' | 'g' | 'b'
}
amount: {
red: number
green: number
blue: number
}
}
const props = defineProps<RGBNodeProps>()
const emit = defineEmits(['change'])
let color = 'red'
switch (props.data?.color) {
case 'r':
color = 'red'
break
case 'g':
color = 'green'
break
case 'b':
color = 'blue'
break
}
const onChange = (e: any) => emit('change', { color, val: e.target.value })
</script>
<template>
<div class="px-4 py-2 bg-white rounded-md border-2 border-solid border-black text-left transform scale-75 lg:scale-100">
<div class="text-md" :style="{ color }">{{ `${color} Amount`.toUpperCase() }}</div>
<input
v-model="props.amount[color]"
class="slider nodrag"
:style="{ '--color': color }"
type="range"
min="0"
max="255"
@input="onChange"
/>
<Handle type="source" :position="Position.Right" :style="{ backgroundColor: color }" />
</div>
</template>
+21
View File
@@ -0,0 +1,21 @@
<script lang="ts" setup>
import type { NodeProps } from '@braks/vue-flow'
import { Handle, Position } from '@braks/vue-flow'
interface RBGOutputNodeProps extends NodeProps {
rgb: string
}
const props = defineProps<RBGOutputNodeProps>()
</script>
<template>
<div
:style="{ backgroundColor: props.rgb }"
class="px-6 py-2 rounded-xl text-white text-center min-w-[220px] border-2 border-white"
>
<div class="text-xl font-bold">Color Output</div>
<div class="font-semibold">{{ props.rgb }}</div>
<Handle type="target" :position="Position.Left" />
</div>
</template>