mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-13 16:05:00 +00:00
feat: improve inputs handling for large list on apps
This commit is contained in:
@@ -423,10 +423,29 @@
|
||||
{#each actionButtons as actionButton, actionIndex (actionButton?.id)}
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<RowWrapper
|
||||
bind:inputs
|
||||
value={row.original}
|
||||
index={rowIndex}
|
||||
onInputsChange={() => {
|
||||
on:set={(e) => {
|
||||
const { id, value } = e.detail
|
||||
if (!inputs[id]) {
|
||||
inputs[id] = { [rowIndex]: value }
|
||||
} else {
|
||||
inputs[id] = { ...inputs[id], [rowIndex]: value }
|
||||
}
|
||||
outputs?.inputs.set(inputs, true)
|
||||
}}
|
||||
on:remove={(e) => {
|
||||
const id = e.detail
|
||||
if (inputs?.[id] == undefined) {
|
||||
return
|
||||
}
|
||||
if (rowIndex == 0) {
|
||||
delete inputs[id]
|
||||
inputs = { ...inputs }
|
||||
} else {
|
||||
delete inputs[id][rowIndex]
|
||||
inputs[id] = { ...inputs[id] }
|
||||
}
|
||||
outputs?.inputs.set(inputs, true)
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
// }
|
||||
const num = isNaN(+values[0]) ? null : +values[0]
|
||||
outputs?.result.set(num)
|
||||
|
||||
if (iterContext && listInputs) {
|
||||
listInputs.set(id, num)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
export let render: boolean
|
||||
export let initializing: boolean | undefined
|
||||
|
||||
const { app, focusedGrid, selectedComponent, worldStore, connectingInput, allIdsInPath } =
|
||||
const { app, focusedGrid, selectedComponent, worldStore, connectingInput, allIdsInPath, mode } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
let page = 0
|
||||
|
||||
@@ -69,19 +69,17 @@
|
||||
initialData: Array<any> | undefined = [],
|
||||
page: number = 0
|
||||
) {
|
||||
const sanitizedData = Array.isArray(initialData) ? initialData : []
|
||||
|
||||
const l = initialData ? initialData.length : 0
|
||||
if (mode === 'auto') {
|
||||
const pageSize: number = configuration.auto.pageSize ?? 0
|
||||
const data = sanitizedData?.slice(0 + page * pageSize, pageSize + page * pageSize) ?? []
|
||||
const shouldDisplayPagination = pageSize < sanitizedData?.length ?? false
|
||||
const total = Math.ceil(sanitizedData.length / pageSize ?? 0)
|
||||
const shouldDisplayPagination = pageSize < l ?? false
|
||||
const total = Math.ceil(l / pageSize ?? 0)
|
||||
|
||||
return {
|
||||
data,
|
||||
shouldDisplayPagination,
|
||||
indexOffset: page * pageSize,
|
||||
disableNext: pageSize > 0 && (page + 1) * pageSize >= sanitizedData?.length,
|
||||
maxIndex: (page + 1) * pageSize - 1,
|
||||
disableNext: pageSize > 0 && (page + 1) * pageSize >= l,
|
||||
total: total
|
||||
}
|
||||
} else {
|
||||
@@ -90,8 +88,8 @@
|
||||
|
||||
return {
|
||||
shouldDisplayPagination: true,
|
||||
data: sanitizedData ?? [],
|
||||
indexOffset: 0,
|
||||
maxIndex: l,
|
||||
disableNext: page + 1 >= pageCount,
|
||||
total: total
|
||||
}
|
||||
@@ -139,43 +137,61 @@
|
||||
bind:loading
|
||||
>
|
||||
<div
|
||||
class={twMerge('flex flex-col divide-y h-full', css?.container?.class, 'wm-list')}
|
||||
class={twMerge('w-full h-full', css?.container?.class, 'wm-list')}
|
||||
style={css?.container?.style}
|
||||
>
|
||||
<div
|
||||
class="w-full flex flex-wrap {$allIdsInPath.includes(id)
|
||||
class="w-full h-full shrink flex flex-wrap {$allIdsInPath.includes(id) && $mode == 'dnd'
|
||||
? 'overflow-visible'
|
||||
: 'overflow-auto'} {isCard
|
||||
? 'h-full gap-2'
|
||||
: resolvedConfig?.displayBorders
|
||||
? 'divide-y max-h-full'
|
||||
: 'max-h-full'}"
|
||||
: 'overflow-auto'} {isCard ? 'gap-2' : resolvedConfig?.displayBorders ? 'divide-y' : ''}"
|
||||
>
|
||||
{#if $app.subgrids?.[`${id}-0`]}
|
||||
{#if Array.isArray(pagination.data) && pagination.data.length > 0}
|
||||
{#each pagination?.data ?? [] as value, index}
|
||||
{#if Array.isArray(result) && result.length > 0}
|
||||
{#each result ?? [] as value, index (index)}
|
||||
{@const inRange = index <= pagination.maxIndex && index >= pagination.indexOffset}
|
||||
<div
|
||||
style={`${
|
||||
isCard
|
||||
? `min-width: ${resolvedConfig.width?.configuration?.card?.minWidthPx}px; `
|
||||
: ''
|
||||
} max-height: ${resolvedConfig.heightPx}px;`}
|
||||
class="{$allIdsInPath.includes(id) ? 'overflow-visible' : 'overflow-auto'} {!isCard
|
||||
? 'w-full'
|
||||
: resolvedConfig?.displayBorders
|
||||
? 'border'
|
||||
: ''}"
|
||||
style={inRange
|
||||
? `${
|
||||
isCard
|
||||
? `min-width: ${resolvedConfig.width?.configuration?.card?.minWidthPx}px; `
|
||||
: ''
|
||||
} max-height: ${resolvedConfig.heightPx}px;`
|
||||
: ''}
|
||||
class={inRange
|
||||
? `${$allIdsInPath.includes(id) ? 'overflow-visible' : 'overflow-auto'} ${
|
||||
!isCard ? 'w-full' : resolvedConfig?.displayBorders ? 'border' : ''
|
||||
}`
|
||||
: 'h-0 float overflow-hidden invisible absolute'}
|
||||
>
|
||||
<ListWrapper
|
||||
onInputsChange={() => {
|
||||
on:set={(e) => {
|
||||
const { id, value } = e.detail
|
||||
if (!inputs[id]) {
|
||||
inputs[id] = { [index]: value }
|
||||
} else {
|
||||
inputs[id] = { ...inputs[id], [index]: value }
|
||||
}
|
||||
outputs?.inputs.set(inputs, true)
|
||||
}}
|
||||
on:remove={(e) => {
|
||||
const id = e.detail
|
||||
if (inputs?.[id] == undefined) {
|
||||
return
|
||||
}
|
||||
if (index == 0) {
|
||||
delete inputs[id]
|
||||
inputs = { ...inputs }
|
||||
} else {
|
||||
delete inputs[id][index]
|
||||
inputs[id] = { ...inputs[id] }
|
||||
}
|
||||
outputs?.inputs.set(inputs, true)
|
||||
}}
|
||||
bind:inputs
|
||||
{value}
|
||||
index={index + pagination.indexOffset}
|
||||
{index}
|
||||
>
|
||||
<SubGridEditor
|
||||
visible={render}
|
||||
visible={render && inRange}
|
||||
{id}
|
||||
subGridId={`${id}-0`}
|
||||
containerHeight={resolvedConfig.heightPx}
|
||||
@@ -190,7 +206,7 @@
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
<ListWrapper onInputsChange={() => {}} disabled value={undefined} index={0}>
|
||||
<ListWrapper disabled value={undefined} index={0}>
|
||||
<SubGridEditor visible={false} {id} subGridId={`${id}-0`} />
|
||||
</ListWrapper>
|
||||
{#if !Array.isArray(result)}
|
||||
@@ -200,7 +216,9 @@
|
||||
{/if}
|
||||
</div>
|
||||
{#if pagination.shouldDisplayPagination}
|
||||
<div class="bg-surface-secondary h-8 flex flex-row gap-1 p-1 items-center wm-list-pagination">
|
||||
<div
|
||||
class="bg-surface-secondary z-20 h-8 flex flex-row gap-1 p-1 items-center wm-list-pagination absolute bottom-0 w-full"
|
||||
>
|
||||
<Button
|
||||
size="xs2"
|
||||
variant="border"
|
||||
|
||||
@@ -1,37 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { setContext } from 'svelte'
|
||||
import { createEventDispatcher, setContext } from 'svelte'
|
||||
import type { ListInputs, ListContext } from '../../types'
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
export let index: number
|
||||
export let value: any
|
||||
export let disabled = false
|
||||
export let inputs: Record<string, Record<number, any>> = {}
|
||||
export let onInputsChange: () => void
|
||||
|
||||
const ctx = writable({ index, value, disabled })
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: $ctx = { index, value, disabled }
|
||||
setContext<ListContext>('ListWrapperContext', ctx)
|
||||
setContext<ListInputs>('ListInputs', {
|
||||
set: (id: string, value: any) => {
|
||||
if (!inputs[id]) {
|
||||
inputs[id] = { [index]: value }
|
||||
} else {
|
||||
inputs[id][index] = value
|
||||
}
|
||||
onInputsChange()
|
||||
dispatch('set', { id, value })
|
||||
},
|
||||
remove(id) {
|
||||
if (inputs?.[id] == undefined) {
|
||||
return
|
||||
}
|
||||
if (index == 0) {
|
||||
delete inputs[id]
|
||||
} else {
|
||||
delete inputs[id][index]
|
||||
}
|
||||
inputs = inputs
|
||||
dispatch('remove', id)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,39 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { setContext } from 'svelte'
|
||||
import { createEventDispatcher, setContext } from 'svelte'
|
||||
import type { ListInputs, ListContext } from '../../types'
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
export let index: number
|
||||
export let value: any
|
||||
export let disabled = false
|
||||
export let inputs: Record<string, Record<number, any>> = {}
|
||||
export let onInputsChange: () => void
|
||||
|
||||
const ctx = writable({ index, value, disabled })
|
||||
|
||||
$: $ctx = { index, value, disabled }
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
setContext<ListContext>('RowWrapperContext', ctx)
|
||||
setContext<ListInputs>('RowInputs', {
|
||||
set: (id: string, value: any) => {
|
||||
if (!inputs[id]) {
|
||||
inputs[id] = { [index]: value }
|
||||
} else {
|
||||
inputs[id][index] = value
|
||||
}
|
||||
inputs = inputs
|
||||
onInputsChange()
|
||||
dispatch('set', { id, value })
|
||||
},
|
||||
remove(id) {
|
||||
if (inputs?.[id] == undefined) {
|
||||
return
|
||||
}
|
||||
if (index == 0) {
|
||||
delete inputs[id]
|
||||
} else {
|
||||
delete inputs[id][index]
|
||||
}
|
||||
inputs = inputs
|
||||
onInputsChange()
|
||||
dispatch('remove', id)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -103,7 +103,6 @@
|
||||
let:dataItem
|
||||
rowHeight={36}
|
||||
cols={columnConfiguration}
|
||||
fastStart={true}
|
||||
gap={[4, 2]}
|
||||
>
|
||||
<ComponentWrapper
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
>
|
||||
<div
|
||||
class={twMerge(
|
||||
$allIdsInPath.includes(id) ? 'overflow-visible' : 'overflow-auto',
|
||||
$allIdsInPath.includes(id) && $mode == 'dnd' ? 'overflow-visible' : 'overflow-auto',
|
||||
noYPadding ? '' : 'py-2',
|
||||
classes ?? '',
|
||||
noPadding ? 'px-0' : 'px-2'
|
||||
@@ -106,7 +106,6 @@
|
||||
let:dataItem
|
||||
rowHeight={36}
|
||||
cols={columnConfiguration}
|
||||
fastStart={true}
|
||||
gap={[4, 2]}
|
||||
scroller={container}
|
||||
parentWidth={$parentWidth - 17}
|
||||
|
||||
@@ -169,7 +169,6 @@ export function settableOutput<T>(state: Writable<number>, previousValue: T): Ou
|
||||
} else {
|
||||
value = x
|
||||
}
|
||||
|
||||
subscribers.forEach((x) => x.next(value!))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
export let rowHeight: number
|
||||
export let cols: [number, number][]
|
||||
export let gap = [10, 10]
|
||||
export let fastStart = false
|
||||
export let throttleUpdate = 100
|
||||
export let throttleResize = 100
|
||||
export let selectedIds: string[] | undefined
|
||||
@@ -171,8 +170,8 @@
|
||||
</script>
|
||||
|
||||
<div class="svlt-grid-container" style="height: {containerHeight}px" bind:this={container}>
|
||||
{#if xPerPx || !fastStart}
|
||||
{#each sortedItems as item (item.id)}
|
||||
{#each sortedItems as item (item.id)}
|
||||
{#if item[getComputedCols] != undefined}
|
||||
<MoveResize
|
||||
on:initmove={handleInitMove}
|
||||
on:move={handleMove}
|
||||
@@ -183,9 +182,10 @@
|
||||
id={item.id}
|
||||
{xPerPx}
|
||||
{yPerPx}
|
||||
width={Math.min(getComputedCols, item[getComputedCols] && item[getComputedCols].w) *
|
||||
xPerPx -
|
||||
gapX * 2}
|
||||
width={xPerPx == 0
|
||||
? 0
|
||||
: Math.min(getComputedCols, item[getComputedCols] && item[getComputedCols].w) * xPerPx -
|
||||
gapX * 2}
|
||||
height={(item[getComputedCols] && item[getComputedCols].h) * yPerPx - gapY * 2}
|
||||
top={(item[getComputedCols] && item[getComputedCols].y) * yPerPx + gapY}
|
||||
left={(item[getComputedCols] && item[getComputedCols].x) * xPerPx + gapX}
|
||||
@@ -201,14 +201,8 @@
|
||||
<slot dataItem={item} item={item[getComputedCols]} />
|
||||
{/if}
|
||||
</MoveResize>
|
||||
{/each}
|
||||
{:else if xPerPx === 0}
|
||||
{#each sortedItems as item (item.id)}
|
||||
<div class="w-0 h-0 overflow-hidden invisible">
|
||||
<slot dataItem={item} item={item[getComputedCols]} />
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, getContext, onMount } from 'svelte'
|
||||
import type { AppEditorContext } from '../types'
|
||||
import type { AppEditorContext, AppViewerContext } from '../types'
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
@@ -28,6 +28,8 @@
|
||||
export let shadow: { x: number; y: number; w: number; h: number } | undefined = undefined
|
||||
|
||||
const ctx = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { mode } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const scale = ctx ? ctx.scale : writable(100)
|
||||
|
||||
const divId = `component-${id}`
|
||||
@@ -50,7 +52,7 @@
|
||||
let anima
|
||||
|
||||
onMount(() => {
|
||||
if (ctx) {
|
||||
if (ctx && $mode == 'dnd') {
|
||||
ctx.dndItem.update((x) => {
|
||||
x[id] = (moveX, moveY, topY) => {
|
||||
ctx.componentActive.set(true)
|
||||
@@ -204,16 +206,18 @@
|
||||
}
|
||||
|
||||
const update = () => {
|
||||
const boundX = capturePos.x + cordDiff.x
|
||||
const _newScrollTop = (scrollElement?.scrollTop ?? 0) - (_scrollTop ?? 0)
|
||||
const boundY = capturePos.y + (cordDiff.y + _newScrollTop)
|
||||
if (xPerPx != 0) {
|
||||
const boundX = capturePos.x + cordDiff.x
|
||||
const _newScrollTop = (scrollElement?.scrollTop ?? 0) - (_scrollTop ?? 0)
|
||||
const boundY = capturePos.y + (cordDiff.y + _newScrollTop)
|
||||
|
||||
let gridX = Math.round(boundX / xPerPx)
|
||||
let gridY = Math.round(boundY / yPerPx)
|
||||
let gridX = Math.round(boundX / xPerPx)
|
||||
let gridY = Math.round(boundY / yPerPx)
|
||||
|
||||
if (shadow) {
|
||||
shadow.x = Math.max(Math.min(gridX, cols - shadow.w), 0)
|
||||
shadow.y = Math.max(gridY, 0)
|
||||
if (shadow) {
|
||||
shadow.x = Math.max(Math.min(gridX, cols - shadow.w), 0)
|
||||
shadow.y = Math.max(gridY, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,9 +325,11 @@
|
||||
// Limit bound
|
||||
newSize.width = Math.min(newSize.width, maxWidth * xPerPx - gapX * 2)
|
||||
|
||||
// Limit col & row
|
||||
shadow.w = Math.round(Math.max((newSize.width + gapX * 2) / xPerPx, 1))
|
||||
shadow.h = Math.round(Math.max((newSize.height + gapY * 2) / yPerPx, 1))
|
||||
if (xPerPx) {
|
||||
// Limit col & row
|
||||
shadow.w = Math.round(Math.max((newSize.width + gapX * 2) / xPerPx, 1))
|
||||
shadow.h = Math.round(Math.max((newSize.height + gapY * 2) / yPerPx, 1))
|
||||
}
|
||||
|
||||
repaint(false, false)
|
||||
}
|
||||
@@ -346,19 +352,27 @@
|
||||
id={divId}
|
||||
class="svlt-grid-item"
|
||||
class:svlt-grid-active={active || (trans && rect)}
|
||||
style="width: {active ? newSize.width : width}px; height:{active ? newSize.height : height}px;
|
||||
style="width: {xPerPx == 0 ? 0 : active ? newSize.width : width}px; height:{xPerPx == 0
|
||||
? 0
|
||||
: active
|
||||
? newSize.height
|
||||
: height}px;
|
||||
{xPerPx == 0 ? 'overflow: hidden;' : ''}
|
||||
{onTop ? 'z-index: 1000;' : ''}
|
||||
|
||||
{active && rect
|
||||
? `transform: translate(${cordDiff.x}px, ${cordDiff.y}px);top:${rect.top}px;left:${rect.left}px;z-index:10000;`
|
||||
: trans
|
||||
? `transform: translate(${cordDiff.x}px, ${cordDiff.y}px); position:absolute; transition: width 0.2s, height 0.2s;`
|
||||
: `transition: transform 0.1s, opacity 0.1s; transform: translate(${left}px, ${top}px); `} "
|
||||
: `${
|
||||
xPerPx > 0 ? 'transition: transform 0.1s, opacity 0.1s;' : ''
|
||||
} transform: translate(${left}px, ${top}px); `} "
|
||||
>
|
||||
<slot />
|
||||
<div class="svlt-grid-resizer" on:pointerdown={resizePointerDown} />
|
||||
</div>
|
||||
|
||||
{#if (active || trans) && shadow}
|
||||
{#if xPerPx > 0 && (active || trans) && shadow}
|
||||
<div
|
||||
class="svlt-grid-shadow shadow-active"
|
||||
style="width: {shadow.w * xPerPx - gapX * 2}px; height: {shadow.h * yPerPx -
|
||||
|
||||
Reference in New Issue
Block a user