Add connexion animated border effect

This commit is contained in:
Guilhem
2024-10-29 14:32:22 +01:00
parent 7f50d92d34
commit f3d8348bb1
4 changed files with 159 additions and 58 deletions
@@ -9,7 +9,8 @@
import type { PropPickerWrapperContext } from './flows/propPicker/PropPickerWrapper.svelte'
import { codeToStaticTemplate, getDefaultExpr } from './flows/utils'
import SimpleEditor from './SimpleEditor.svelte'
import { Button } from './common'
import { Button } from '$lib/components/common'
import AnimatedButton from '$lib/components/common/button/AnimatedButton.svelte'
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
import { fade } from 'svelte/transition'
@@ -187,6 +188,9 @@
let stepInputGen: StepInputGen | undefined = undefined
loadResourceTypes()
$: connecting =
$propPickerConfig?.propName == argName && $propPickerConfig?.insertionMode == 'connect'
</script>
{#if arg != undefined}
@@ -331,22 +335,23 @@
</ToggleButtonGroup>
</div>
<Button
title="Connect to another node's output"
variant="border"
color="light"
size="xs2"
on:click={() => {
focusProp(argName, 'connect', (path) => {
connectProperty(path)
dispatch('change', { argName })
return true
})
}}
id="flow-editor-plug"
>
<Plug size={16} /> &rightarrow;
</Button>
<AnimatedButton animate={connecting} baseRadius="6px" animationDuration="1s">
<Button
variant="border"
color="light"
size="xs2"
btnClasses={connecting ? 'text-blue-500' : 'text-primary'}
on:click={() => {
focusProp(argName, 'connect', (path) => {
connectProperty(path)
dispatch('change', { argName })
return true
})
}}
>
<Plug size={16} /> &rightarrow;
</Button>
</AnimatedButton>
</div>
{/if}
</div>
@@ -354,13 +359,13 @@
<div class="max-w-xs" />
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="relative" on:keyup={stepInputGen?.onKeyUp}>
{#if $propPickerConfig?.propName == argName && $propPickerConfig?.insertionMode == 'connect'}
<!-- {#if $propPickerConfig?.propName == argName && $propPickerConfig?.insertionMode == 'connect'}
<span
class={'text-white z-50 px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute top-0 right-0 bg-blue-500'}
>
Connect input &rightarrow;
</span>
{/if}
{/if} -->
<!-- {inputCat}
{propertyType} -->
<div class="relative flex flex-row items-top gap-2 justify-between">
@@ -471,7 +476,7 @@
{/if}
</div>
{#if $propPickerConfig?.propName == argName}
{#if $propPickerConfig?.propName == argName && $propPickerConfig?.insertionMode == 'insert'}
<div class="text-blue-500 mt-2" in:fade={{ duration: 200 }}>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -0,0 +1,85 @@
<script lang="ts">
import { twMerge } from 'tailwind-merge'
export let marginWidth = '2px'
export let animationDuration = '2s'
export let baseRadius = '4px'
export let animate = true
export let wrapperClasses = ''
export let ringColor = 'transparent'
let clientWidth = 0
let clientHeight = 0
$: circleRadius = Math.sqrt(clientWidth * clientWidth + clientHeight * clientHeight) / 2
</script>
<div
class={twMerge('gradient-button', wrapperClasses)}
style="--margin-width: {marginWidth}; --animation-duration: {animationDuration}; --base-radius: {baseRadius}; --circle-radius: {circleRadius}; --ring-color: {ringColor}"
class:animate
bind:clientWidth
bind:clientHeight
>
<slot />
</div>
<style>
.gradient-button {
position: relative;
padding: var(--margin-width, 2px);
font-size: inherit;
border: none;
border-radius: calc(var(--base-radius) + var(--margin-width, 2px));
color: currentColor;
background: inherit;
z-index: 1;
overflow: hidden;
}
/* Circular gradient */
.gradient-button::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: calc(var(--circle-radius, 300px) * 2px);
height: calc(var(--circle-radius, 300px) * 2px);
background: var(--ring-color, transparent);
border-radius: 50%;
z-index: -1;
animation: none;
}
.gradient-button.animate::before {
background: conic-gradient(from 0deg, #63a9ff, #0c79fd, #033b80, #00142c, #63a9ff);
animation: rotate var(--animation-duration, 2s) linear infinite;
}
/* inner background */
.gradient-button::after {
content: '';
position: absolute;
top: var(--margin-width, 2px);
right: var(--margin-width, 2px);
bottom: var(--margin-width, 2px);
left: var(--margin-width, 2px);
background: inherit;
border-radius: var(--base-radius);
z-index: -1;
}
@keyframes rotate {
from {
transform: translate(-50%, -50%) rotate(0deg);
}
to {
transform: translate(-50%, -50%) rotate(360deg);
}
}
.gradient-button.animate:hover::before {
animation-duration: 1s;
}
</style>
@@ -25,6 +25,7 @@
import { writable, type Writable } from 'svelte/store'
import type { PickableProperties } from '../previousResults'
import { twMerge } from 'tailwind-merge'
import AnimatedButton from '$lib/components/common/button/AnimatedButton.svelte'
export let pickableProperties: PickableProperties | undefined
export let result: any = undefined
@@ -62,41 +63,44 @@
<Pane class={twMerge('relative !transition-none ', noPadding ? '' : 'p-2')}>
<slot />
</Pane>
<Pane
minSize={20}
size={40}
class="pt-2 relative ml-[-1px] border-4 !transition-none z-1000 {$propPickerConfig
? 'rounded-l-md border-blue-500'
: 'border-transparent'}"
>
{#if result}
<PropPickerResult
{result}
{extraResults}
{flow_input}
allowCopy={!notSelectable && !$propPickerConfig}
on:select={({ detail }) => {
dispatch('select', detail)
if ($propPickerConfig?.onSelect(detail)) {
propPickerConfig.set(undefined)
}
}}
/>
{:else if pickableProperties}
<PropPicker
{displayContext}
{error}
{pickableProperties}
{notSelectable}
allowCopy={!notSelectable && !$propPickerConfig}
on:select={({ detail }) => {
dispatch('select', detail)
if ($propPickerConfig?.onSelect(detail)) {
propPickerConfig.set(undefined)
}
}}
/>
{/if}
<Pane minSize={20} size={40} class="!transition-none z-1000 ml-[-1px]">
<AnimatedButton
animate={$propPickerConfig?.insertionMode == 'connect'}
baseRadius="4px"
wrapperClasses="h-full w-full pt-2"
marginWidth="4px"
ringColor={$propPickerConfig?.insertionMode == 'insert' ? '#3b82f6' : 'transparent'}
animationDuration="1s"
>
{#if result}
<PropPickerResult
{result}
{extraResults}
{flow_input}
allowCopy={!notSelectable && !$propPickerConfig}
on:select={({ detail }) => {
dispatch('select', detail)
if ($propPickerConfig?.onSelect(detail)) {
propPickerConfig.set(undefined)
}
}}
/>
{:else if pickableProperties}
<PropPicker
{displayContext}
{error}
{pickableProperties}
{notSelectable}
allowCopy={!notSelectable && !$propPickerConfig}
on:select={({ detail }) => {
dispatch('select', detail)
if ($propPickerConfig?.onSelect(detail)) {
propPickerConfig.set(undefined)
}
}}
/>
{/if}
</AnimatedButton>
</Pane>
</Splitpanes>
</div>
@@ -63,12 +63,19 @@
}
</script>
<div class="flex flex-col h-full !bg-surface">
<div class="flex flex-col h-full !bg-surface rounded overflow-hidden">
<div class="px-2">
{#if !notSelectable}
<div class="flex flex-row space-x-1">
{#if $propPickerConfig}
<!-- <Badge large color="blue">
{`Selected: ${$propPickerConfig?.propName}`}
</Badge> -->
<Badge large color="blue">
{`Mode: ${$propPickerConfig?.insertionMode}`}
</Badge>
{:else}
<Badge large color="blue">&leftarrow; Edit or connect an input</Badge>
</div>
{/if}
{/if}
<ClearableInput bind:value={search} placeholder="Search prop..." wrapperClass="py-2" />
</div>