mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-08 16:02:30 +00:00
feat(frontend): add expand (#1268)
* feat(frontend): add expand * feat(frontend): fix container height * feat(frontend): remove code duplication * feat(frontend): add historic
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
import type { AppEditorContext } from '../types'
|
||||
import { Anchor, Bug, Move } from 'lucide-svelte'
|
||||
import { Anchor, Bug, Expand, Move } from 'lucide-svelte'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
import { Alert, Button } from '$lib/components/common'
|
||||
@@ -55,6 +55,17 @@
|
||||
<Anchor aria-label="Lock position" size={14} />
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
title="Position locking"
|
||||
class={classNames(
|
||||
'text-gray-800 px-1 text-2xs py-0.5 font-bold w-fit shadow border border-gray-300 absolute -top-1 right-[0.5rem] z-50 cursor-pointer',
|
||||
' hover:bg-gray-300',
|
||||
selected ? 'bg-gray-200/80' : 'bg-gray-200/80'
|
||||
)}
|
||||
on:click={() => dispatch('expand')}
|
||||
>
|
||||
<Expand aria-label="Lock position" size={14} />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if selected || hover}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import Component from './component/Component.svelte'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { push } from '$lib/history'
|
||||
import { expandGriditem, findAvailableSpace, findGridItem } from './appUtils'
|
||||
|
||||
export let policy: Policy
|
||||
|
||||
@@ -25,7 +26,8 @@
|
||||
summary,
|
||||
focusedGrid,
|
||||
parentWidth,
|
||||
history
|
||||
history,
|
||||
breakpoint
|
||||
} = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
// The drag is disabled when the user is connecting an input
|
||||
@@ -203,11 +205,16 @@
|
||||
locked={isFixed(gridComponent)}
|
||||
on:delete={() => removeGridElement(gridComponent.data)}
|
||||
on:lock={() => {
|
||||
let fComponent = $app.grid.find((c) => c.id === gridComponent.id)
|
||||
if (fComponent) {
|
||||
fComponent = toggleFixed(fComponent)
|
||||
const gridItem = findGridItem($app, gridComponent.data.id)
|
||||
if (gridItem) {
|
||||
toggleFixed(gridItem)
|
||||
}
|
||||
}}
|
||||
on:expand={() => {
|
||||
push(history, $app)
|
||||
expandGriditem($app.grid, gridComponent, $breakpoint)
|
||||
$app = { ...$app }
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
import { columnConfiguration, isFixed, toggleFixed } from '../gridUtils'
|
||||
import type { AppEditorContext, GridItem } from '../types'
|
||||
import Component from './component/Component.svelte'
|
||||
import { findGridItem } from './appUtils'
|
||||
import { expandGriditem, findAvailableSpace, findGridItem } from './appUtils'
|
||||
import { push } from '$lib/history'
|
||||
|
||||
export let containerHeight: number
|
||||
let classes = ''
|
||||
@@ -21,8 +22,16 @@
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const { app, connectingInput, selectedComponent, focusedGrid, mode, parentWidth } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
const {
|
||||
app,
|
||||
connectingInput,
|
||||
selectedComponent,
|
||||
focusedGrid,
|
||||
mode,
|
||||
parentWidth,
|
||||
breakpoint,
|
||||
history
|
||||
} = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
$: highlight = id === $focusedGrid?.parentComponentId && shouldHighlight
|
||||
|
||||
@@ -129,6 +138,18 @@
|
||||
selected={$selectedComponent === dataItem.data.id}
|
||||
locked={isFixed(gridComponent)}
|
||||
on:lock={() => lock(gridComponent)}
|
||||
on:expand={() => {
|
||||
const parentGridItem = findGridItem($app, id)
|
||||
|
||||
if (!parentGridItem) {
|
||||
return
|
||||
}
|
||||
|
||||
push(history, $app)
|
||||
|
||||
expandGriditem(subGrid, gridComponent, $breakpoint, parentGridItem)
|
||||
$app = { ...$app }
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getNextId } from '$lib/components/flows/flowStateUtils'
|
||||
import type { App, FocusedGrid, GridItem } from '../types'
|
||||
import type { App, 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'
|
||||
@@ -31,7 +31,6 @@ export function getNextGridItemId(app: App): string {
|
||||
}
|
||||
|
||||
export function createNewGridItem(grid: GridItem[], id: string, data: AppComponent): GridItem {
|
||||
|
||||
const appComponent = data
|
||||
|
||||
appComponent.id = id
|
||||
@@ -176,3 +175,131 @@ export function duplicateGridItem(
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
type AvailableSpace = {
|
||||
left: number
|
||||
right: number
|
||||
top: number
|
||||
bottom: number
|
||||
}
|
||||
|
||||
export function findAvailableSpace(
|
||||
grid: GridItem[],
|
||||
gridItem: GridItem,
|
||||
editorBreakpoint: EditorBreakpoint,
|
||||
parentGridItem: GridItem | undefined = undefined
|
||||
): AvailableSpace | undefined {
|
||||
if (gridItem) {
|
||||
const breakpoint = editorBreakpoint === 'sm' ? 3 : 12
|
||||
const maxHeight = parentGridItem ? parentGridItem[breakpoint].h - 1 : 12
|
||||
const maxWidth = 12
|
||||
|
||||
const availableSpace = {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0
|
||||
}
|
||||
|
||||
const items = grid.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
x: item[breakpoint].x,
|
||||
y: item[breakpoint].y,
|
||||
w: item[breakpoint].w,
|
||||
h: item[breakpoint].h
|
||||
}
|
||||
})
|
||||
|
||||
const item = items.find((item) => item.id === gridItem.id)
|
||||
|
||||
if (!item) {
|
||||
return availableSpace
|
||||
}
|
||||
|
||||
if (item.x > 0) {
|
||||
for (let x = item.x - 1; x >= 0; x--) {
|
||||
const itemToCheck = { ...item, x, w: 1 }
|
||||
const isItemInWay = items.some((item) => isOverlapping(item, itemToCheck))
|
||||
|
||||
if (isItemInWay) {
|
||||
break
|
||||
} else {
|
||||
availableSpace.left++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.x + item.w < maxWidth) {
|
||||
for (let x = item.x + item.w; x < maxWidth; x++) {
|
||||
const itemToCheck = { ...item, x, w: 1 }
|
||||
const isItemInWay = items.some((item) => isOverlapping(item, itemToCheck))
|
||||
|
||||
if (isItemInWay) {
|
||||
break
|
||||
} else {
|
||||
availableSpace.right++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.y > 0) {
|
||||
for (let y = item.y - 1; y >= 0; y--) {
|
||||
const itemToCheck = { ...item, h: 1, y }
|
||||
const isItemInWay = items.some((item) => isOverlapping(item, itemToCheck))
|
||||
|
||||
if (isItemInWay) {
|
||||
break
|
||||
} else {
|
||||
availableSpace.top++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.y + item.h < maxHeight) {
|
||||
for (let y = item.y + item.h; y < maxHeight; y++) {
|
||||
const itemToCheck = { ...item, h: 1, y }
|
||||
const isItemInWay = items.some((item) => isOverlapping(item, itemToCheck))
|
||||
|
||||
if (isItemInWay) {
|
||||
break
|
||||
} else {
|
||||
availableSpace.bottom++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return availableSpace
|
||||
}
|
||||
}
|
||||
|
||||
function isOverlapping(item1: any, item2: any) {
|
||||
return (
|
||||
item1.x < item2.x + item2.w &&
|
||||
item1.x + item1.w > item2.x &&
|
||||
item1.y < item2.y + item2.h &&
|
||||
item1.y + item1.h > item2.y
|
||||
)
|
||||
}
|
||||
|
||||
export function expandGriditem(
|
||||
grid: GridItem[],
|
||||
gridComponent: GridItem,
|
||||
$breakpoint: EditorBreakpoint,
|
||||
parentGridItem: GridItem | undefined = undefined
|
||||
) {
|
||||
const availableSpace = findAvailableSpace(grid, gridComponent, $breakpoint, parentGridItem)
|
||||
|
||||
if (!availableSpace) {
|
||||
return
|
||||
}
|
||||
|
||||
const { left, right, top, bottom } = availableSpace
|
||||
const width = $breakpoint === 'sm' ? 3 : 12
|
||||
const previousGridItem = JSON.parse(JSON.stringify(gridComponent[width]))
|
||||
|
||||
gridComponent[width].x = previousGridItem.x - left
|
||||
gridComponent[width].y = previousGridItem.y - top
|
||||
gridComponent[width].w = previousGridItem.w + left + right
|
||||
gridComponent[width].h = previousGridItem.h + top + bottom
|
||||
}
|
||||
|
||||
@@ -69,7 +69,16 @@
|
||||
class="h-full flex flex-col w-full component"
|
||||
>
|
||||
{#if $mode !== 'preview'}
|
||||
<ComponentHeader {hover} {pointerdown} {component} {selected} on:delete on:lock {locked} />
|
||||
<ComponentHeader
|
||||
{hover}
|
||||
{pointerdown}
|
||||
{component}
|
||||
{selected}
|
||||
on:delete
|
||||
on:lock
|
||||
on:expand
|
||||
{locked}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
|
||||
@@ -237,65 +237,6 @@ export function toKebabCase(text: string) {
|
||||
return text.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
|
||||
}
|
||||
|
||||
export function findParent(root: GridItem[], id: string): GridItem | undefined {
|
||||
for (const a of root) {
|
||||
if (a.id === id) {
|
||||
return a
|
||||
}
|
||||
|
||||
if (a.data.subGrids) {
|
||||
// Recursively search the sub-grids
|
||||
for (const subGrid of a.data.subGrids) {
|
||||
const result = findParent(subGrid, id)
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function insertNewGridItem(
|
||||
root: GridItem[],
|
||||
id: string,
|
||||
subGridIndex: number,
|
||||
newId: string,
|
||||
data: AppComponent
|
||||
): GridItem[] {
|
||||
const parentA = findParent(root, id)
|
||||
|
||||
if (!parentA) {
|
||||
throw new Error(`Parent A object with ID ${id} not found.`)
|
||||
}
|
||||
|
||||
const subGrid = parentA.data.subGrids[subGridIndex]
|
||||
|
||||
if (!subGrid) {
|
||||
throw new Error(`Sub-grid with index ${subGridIndex} not found.`)
|
||||
}
|
||||
|
||||
const newItem = createNewGridItem(subGrid ?? [], newId, data)
|
||||
subGrid.push(newItem)
|
||||
return root
|
||||
}
|
||||
|
||||
|
||||
function recursiveGetIds(gridItem: GridItem): string[] {
|
||||
const subGrids = gridItem.data.subGrids ?? []
|
||||
const subGridIds = subGrids
|
||||
.map((subGrid: GridItem[]) => subGrid.map(recursiveGetIds))
|
||||
.flat(Infinity)
|
||||
return [gridItem.data.id, ...subGridIds]
|
||||
}
|
||||
|
||||
export function getNextGridItemId(grid: GridItem[] = []): string {
|
||||
const gridItemIds = grid.map(recursiveGetIds).flat()
|
||||
const id = getNextId(gridItemIds)
|
||||
return id
|
||||
}
|
||||
|
||||
export function concatCustomCss<T extends string = string>(
|
||||
appCss?: ComponentCustomCSS<T>,
|
||||
componentCss?: ComponentCustomCSS<T>
|
||||
|
||||
Reference in New Issue
Block a user