mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 16:00:38 +00:00
feat(frontend): improving connection
This commit is contained in:
@@ -185,13 +185,13 @@
|
||||
{:else}
|
||||
<SplitPanesWrapper>
|
||||
<Splitpanes class="max-w-full overflow-hidden">
|
||||
<Pane size={$connectingInput?.opened ? 40 : 15} minSize={5} maxSize={33}>
|
||||
<Pane size={15} minSize={5} maxSize={33}>
|
||||
<ContextPanel />
|
||||
</Pane>
|
||||
<Pane size={64}>
|
||||
<SplitPanesWrapper>
|
||||
<Splitpanes horizontal>
|
||||
<Pane size={$connectingInput?.opened ? 100 : 70}>
|
||||
<Pane size={70}>
|
||||
<div
|
||||
on:pointerdown={(e) => {
|
||||
$selectedComponent = undefined
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getNextId } from '$lib/components/flows/flowStateUtils'
|
||||
import type { App, EditorBreakpoint, FocusedGrid, GridItem } from '../types'
|
||||
import type { App, ConnectingInput, EditorBreakpoint, FocusedGrid, GridItem } from '../types'
|
||||
import { getRecommendedDimensionsByComponent, type AppComponent } from './component'
|
||||
import gridHelp from '@windmill-labs/svelte-grid/src/utils/helper'
|
||||
import { gridColumns } from '../gridUtils'
|
||||
@@ -39,7 +39,7 @@ export function isIdInsideGriditem(app: App, gridItem: GridItem, id: string | un
|
||||
let currentId = id
|
||||
while (currentId) {
|
||||
path.push(currentId)
|
||||
currentId = findGridItemParentId(app, currentId)
|
||||
currentId = findGridItemParentGrid(app, currentId)?.split('-')[0]
|
||||
}
|
||||
|
||||
return path.includes(gridItem.id)
|
||||
@@ -58,7 +58,6 @@ export function getNextGridItemId(app: App): string {
|
||||
}
|
||||
|
||||
export function createNewGridItem(grid: GridItem[], id: string, data: AppComponent): GridItem {
|
||||
|
||||
const newComponent = {
|
||||
resizable: true,
|
||||
draggable: true,
|
||||
@@ -116,7 +115,6 @@ export function insertNewGridItem(
|
||||
app.subgrids = {}
|
||||
}
|
||||
|
||||
|
||||
// We only want to set subgrids when we are not moving
|
||||
if (!keepId) {
|
||||
for (let i = 0; i < (data.numberOfSubgrids ?? 0); i++) {
|
||||
@@ -124,8 +122,9 @@ export function insertNewGridItem(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const key = focusedGrid ? `${focusedGrid?.parentComponentId}-${focusedGrid?.subGridIndex ?? 0}` : undefined
|
||||
const key = focusedGrid
|
||||
? `${focusedGrid?.parentComponentId}-${focusedGrid?.subGridIndex ?? 0}`
|
||||
: undefined
|
||||
let grid = focusedGrid ? app.subgrids[key!] : app.grid
|
||||
|
||||
const newItem = createNewGridItem(grid, id, data)
|
||||
@@ -188,8 +187,6 @@ export function deleteGridItem(
|
||||
return components
|
||||
}
|
||||
|
||||
|
||||
|
||||
type AvailableSpace = {
|
||||
left: number
|
||||
right: number
|
||||
@@ -297,11 +294,14 @@ function isOverlapping(item1: any, item2: any) {
|
||||
}
|
||||
|
||||
type Outputtable<Type> = {
|
||||
-readonly [Property in keyof Type]: Output<Type[Property]>;
|
||||
};
|
||||
-readonly [Property in keyof Type]: Output<Type[Property]>
|
||||
}
|
||||
|
||||
|
||||
export function initOutput<I extends Record<string, any>>(world: World, id: string, init: I): Outputtable<I> {
|
||||
export function initOutput<I extends Record<string, any>>(
|
||||
world: World,
|
||||
id: string,
|
||||
init: I
|
||||
): Outputtable<I> {
|
||||
const output = world.outputsById[id] as Outputtable<I>
|
||||
if (init) {
|
||||
for (const key in init) {
|
||||
@@ -333,3 +333,53 @@ export function expandGriditem(
|
||||
item.w = item.w + left + right
|
||||
item.h = item.h + top + bottom
|
||||
}
|
||||
|
||||
export function sortGridItemsPosition(
|
||||
gridItems: GridItem[],
|
||||
breakpoint: EditorBreakpoint
|
||||
): GridItem[] {
|
||||
return gridItems.sort((a: GridItem, b: GridItem) => {
|
||||
const width = breakpoint === 'lg' ? 12 : 3
|
||||
|
||||
const aX = a[width].x
|
||||
const aY = a[width].y
|
||||
const bX = b[width].x
|
||||
const bY = b[width].y
|
||||
|
||||
if (aY < bY) {
|
||||
return -1
|
||||
} else if (aY > bY) {
|
||||
return 1
|
||||
} else {
|
||||
if (aX < bX) {
|
||||
return -1
|
||||
} else if (aX > bX) {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function connectInput(
|
||||
connectingInput: ConnectingInput,
|
||||
componentId: string,
|
||||
path: string
|
||||
): ConnectingInput {
|
||||
if (connectingInput) {
|
||||
connectingInput = {
|
||||
opened: false,
|
||||
input: {
|
||||
connection: {
|
||||
componentId,
|
||||
path
|
||||
},
|
||||
type: 'connected'
|
||||
},
|
||||
hoveredComponent: undefined
|
||||
}
|
||||
}
|
||||
|
||||
return connectingInput
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import {
|
||||
deleteGridItem,
|
||||
findGridItem,
|
||||
findGridItemParentGrid,
|
||||
getAllSubgridsAndComponentIds,
|
||||
getGridItems,
|
||||
insertNewGridItem
|
||||
insertNewGridItem,
|
||||
sortGridItemsPosition
|
||||
} from '../appUtils'
|
||||
import type { AppEditorContext, AppViewerContext, EditorBreakpoint, GridItem } from '../../types'
|
||||
import { push } from '$lib/history'
|
||||
@@ -109,6 +108,7 @@
|
||||
function handleArrowUp(event: KeyboardEvent) {
|
||||
if (!$selectedComponent) return
|
||||
let parentId = findGridItemParentGrid($app, $selectedComponent)?.split('-')[0]
|
||||
|
||||
if (parentId) {
|
||||
$selectedComponent = parentId
|
||||
} else {
|
||||
@@ -182,6 +182,7 @@
|
||||
|
||||
case 'ArrowUp': {
|
||||
handleArrowUp(event)
|
||||
break
|
||||
}
|
||||
|
||||
case 'ArrowDown': {
|
||||
@@ -221,31 +222,6 @@
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function sortGridItemsPosition(gridItems: GridItem[], breakpoint: EditorBreakpoint): GridItem[] {
|
||||
return gridItems.sort((a: GridItem, b: GridItem) => {
|
||||
const width = breakpoint === 'lg' ? 12 : 3
|
||||
|
||||
const aX = a[width].x
|
||||
const aY = a[width].y
|
||||
const bX = b[width].x
|
||||
const bY = b[width].y
|
||||
|
||||
if (aY < bY) {
|
||||
return -1
|
||||
} else if (aY > bY) {
|
||||
return 1
|
||||
} else {
|
||||
if (aX < bX) {
|
||||
return -1
|
||||
} else if (aX > bX) {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={keydown} />
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
import type { AppViewerContext, GridItem } from '../../types'
|
||||
import ComponentOutputViewer from './ComponentOutputViewer.svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { ChevronDown, ChevronRight, FolderOpen } from 'lucide-svelte'
|
||||
import { isIdInsideGriditem } from '../appUtils'
|
||||
import { ChevronDown, ChevronRight, Lock } from 'lucide-svelte'
|
||||
import { connectInput, isIdInsideGriditem } from '../appUtils'
|
||||
import { slide } from 'svelte/transition'
|
||||
import SubGridOutput from './SubGridOutput.svelte'
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
export let parentId: string | undefined = undefined
|
||||
export let expanded: boolean = false
|
||||
|
||||
const { app, staticOutputs, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app, staticOutputs, selectedComponent, connectingInput } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
const name = getComponentNameById(gridItem.id)
|
||||
|
||||
function getComponentNameById(componentId: string) {
|
||||
@@ -84,16 +85,24 @@
|
||||
{getComponentNameById(gridItem.id)}
|
||||
{#if !opened && !manuallyOpened}
|
||||
<ChevronRight size={14} />
|
||||
{:else if manuallyOpened}
|
||||
<Lock size={14} class="text-orange-600" />
|
||||
{:else}
|
||||
<ChevronDown size={14} class={manuallyOpened ? 'text-indigo-500 ' : ''} />
|
||||
<ChevronDown size={14} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if opened}
|
||||
<div class={classNames('border-b', nested ? 'border-l' : '')} transition:slide|local>
|
||||
<div class={classNames('py-1')}>
|
||||
<ComponentOutputViewer componentId={gridItem.id} outputs={$staticOutputs[gridItem.id]} />
|
||||
<div class="py-1">
|
||||
<ComponentOutputViewer
|
||||
componentId={gridItem.id}
|
||||
outputs={$staticOutputs[gridItem.id]}
|
||||
on:select={({ detail }) => {
|
||||
$connectingInput = connectInput($connectingInput, gridItem.id, detail)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
@@ -1,31 +1,18 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { Maximize, Minimize, X } from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import { findGridItem } from '../appUtils'
|
||||
import { connectInput, findGridItem, sortGridItemsPosition } from '../appUtils'
|
||||
import { components } from '../component'
|
||||
import PanelSection from '../settingsPanel/common/PanelSection.svelte'
|
||||
import ComponentOutput from './ComponentOutput.svelte'
|
||||
import ComponentOutputViewer from './ComponentOutputViewer.svelte'
|
||||
|
||||
const { connectingInput, staticOutputs, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
function connectInput(componentId: string, path: string) {
|
||||
if ($connectingInput) {
|
||||
$connectingInput = {
|
||||
opened: false,
|
||||
input: {
|
||||
connection: {
|
||||
componentId,
|
||||
path
|
||||
},
|
||||
type: 'connected'
|
||||
},
|
||||
hoveredComponent: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
const { staticOutputs, app, breakpoint, connectingInput, selectedComponent } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
function getComponentNameById(componentId: string) {
|
||||
const component = findGridItem($app, componentId)
|
||||
@@ -41,12 +28,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
function toggleExpanded() {
|
||||
expanded = !expanded
|
||||
}
|
||||
|
||||
let search = ''
|
||||
let expanded = false
|
||||
|
||||
$: panels = [['ctx', ['email', 'username', 'query', 'hash']] as [string, string[]]].concat(
|
||||
Object.entries($staticOutputs)
|
||||
)
|
||||
|
||||
let search = ''
|
||||
|
||||
// filter out outputs that don't match the search by name (computed by getComponentNameById) and id
|
||||
// The output should be [string, string[]][]
|
||||
$: filteredPanels = panels.filter(([componentId, outputs]) => {
|
||||
@@ -56,46 +48,58 @@
|
||||
componentId.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
})
|
||||
|
||||
let expanded = false
|
||||
|
||||
function toggleExpanded() {
|
||||
expanded = !expanded
|
||||
}
|
||||
</script>
|
||||
|
||||
<PanelSection noPadding titlePadding="px-4 pt-2 pb-0.5" title="Outputs">
|
||||
<div class="overflow-auto h-full min-w-[150px] w-full relative flex flex-col">
|
||||
<div class="sticky z-50 top-0 left-0 w-full bg-white px-2 pb-2">
|
||||
<div class="relative">
|
||||
<input
|
||||
bind:value={search}
|
||||
class="px-2 py-1 border border-gray-300 rounded-sm {search ? 'pr-8' : ''}"
|
||||
placeholder="Search outputs..."
|
||||
/>
|
||||
{#if search}
|
||||
<button
|
||||
class="absolute right-2 top-1/2 transform -translate-y-1/2 hover:bg-gray-200 rounded-full p-0.5"
|
||||
on:click|stopPropagation|preventDefault={() => (search = '')}
|
||||
>
|
||||
<X size="14" />
|
||||
</button>
|
||||
{/if}
|
||||
<div style="z-index:1000;" class="bg-white">
|
||||
<div class="overflow-auto h-full min-w-[150px] w-full relative flex flex-col">
|
||||
<div class="sticky z-50 top-0 left-0 w-full bg-white px-2 pb-2">
|
||||
<div class="relative">
|
||||
<input
|
||||
bind:value={search}
|
||||
class="px-2 py-1 border border-gray-300 rounded-sm {search ? 'pr-8' : ''}"
|
||||
placeholder="Search outputs..."
|
||||
/>
|
||||
{#if search}
|
||||
<button
|
||||
class="absolute right-2 top-1/2 transform -translate-y-1/2 hover:bg-gray-200 rounded-full p-0.5"
|
||||
on:click|stopPropagation|preventDefault={() => (search = '')}
|
||||
>
|
||||
<X size="14" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-1 ">
|
||||
<Button on:click={toggleExpanded} color="light" size="xs">
|
||||
{#if !expanded}
|
||||
<Maximize size="14" />
|
||||
{:else}
|
||||
<Minimize size="14" />
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="p-1 ">
|
||||
<Button on:click={toggleExpanded} color="light" size="xs">
|
||||
{#if !expanded}
|
||||
<Maximize size="14" />
|
||||
{:else}
|
||||
<Minimize size="14" />
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{#each $app.grid as gridItem, index}
|
||||
<ComponentOutput {gridItem} first={index === 0} {expanded} />
|
||||
{/each}
|
||||
<div
|
||||
class={classNames(
|
||||
'text-2xs ml-0.5 font-bold px-2 py-0.5 rounded-sm',
|
||||
$selectedComponent === 'ctx' ? 'bg-indigo-500 text-white' : ' bg-indigo-50'
|
||||
)}
|
||||
>
|
||||
ctx
|
||||
</div>
|
||||
<ComponentOutputViewer
|
||||
componentId={'ctx'}
|
||||
outputs={['email', 'username', 'query', 'hash']}
|
||||
on:select={({ detail }) => {
|
||||
$connectingInput = connectInput($connectingInput, 'ctx', detail)
|
||||
}}
|
||||
/>
|
||||
|
||||
{#each sortGridItemsPosition($app.grid, $breakpoint) as gridItem, index}
|
||||
<ComponentOutput {gridItem} first={index === 0} {expanded} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</PanelSection>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { slide } from 'svelte/transition'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import { connectInput, sortGridItemsPosition } from '../appUtils'
|
||||
import ComponentOutput from './ComponentOutput.svelte'
|
||||
|
||||
export let name: string | undefined = undefined
|
||||
@@ -11,7 +12,8 @@
|
||||
export let expanded: boolean = false
|
||||
export let subGrids: string[]
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app, connectingInput, breakpoint, worldStore } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let selected = 0
|
||||
|
||||
@@ -51,10 +53,16 @@
|
||||
{#if selected === index || name !== 'Tabs'}
|
||||
<div transition:slide|local>
|
||||
{#if $app.subgrids && $app.subgrids[subGridId].length > 0}
|
||||
{#each $app.subgrids[subGridId] as subGridItem, index}
|
||||
{#if subGridItem}
|
||||
<ComponentOutput gridItem={subGridItem} first={index === 0} nested {expanded} />
|
||||
{/if}
|
||||
{#each sortGridItemsPosition($app.subgrids[subGridId], $breakpoint) as subGridItem, index}
|
||||
<ComponentOutput
|
||||
gridItem={subGridItem}
|
||||
first={index === 0}
|
||||
nested
|
||||
{expanded}
|
||||
on:select={({ detail }) => {
|
||||
$connectingInput = connectInput($connectingInput, subGridItem.id, detail)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="text-xs text-gray-500 border-y border-l p-1">No components</div>
|
||||
|
||||
Reference in New Issue
Block a user