mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 08:01:35 +00:00
feat: list component for apps (#1740)
* all * feat: list component for apps
This commit is contained in:
@@ -147,8 +147,8 @@
|
||||
{#if resolvedConfig.beforeIcon && beforeIconComponent}
|
||||
<svelte:component this={beforeIconComponent} size={14} />
|
||||
{/if}
|
||||
{#if resolvedConfig.label && resolvedConfig.label?.length > 0}
|
||||
<div>{resolvedConfig.label}</div>
|
||||
{#if resolvedConfig.label?.toString() && resolvedConfig.label?.toString()?.length > 0}
|
||||
<div>{resolvedConfig.label.toString()}</div>
|
||||
{/if}
|
||||
{#if resolvedConfig.afterIcon && afterIconComponent}
|
||||
<svelte:component this={afterIconComponent} size={14} />
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { isCodeInjection } from '$lib/components/flows/utils'
|
||||
import { createEventDispatcher, getContext, onDestroy, tick } from 'svelte'
|
||||
import type { AppInput, EvalAppInput, UploadAppInput } from '../../inputType'
|
||||
import type { AppViewerContext, RichConfiguration } from '../../types'
|
||||
import type { AppViewerContext, ListContext, RichConfiguration } from '../../types'
|
||||
import { accessPropertyByPath } from '../../utils'
|
||||
import { computeGlobalContext, eval_like } from './eval'
|
||||
import deepEqualWithOrderedArray from './deepEqualWithOrderedArray'
|
||||
@@ -19,6 +19,9 @@
|
||||
|
||||
const { componentControl, runnableComponents } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const iterContext = getContext<ListContext>('ListWrapperContext')
|
||||
|
||||
$: fullContext = iterContext ? { ...extraContext, iter: $iterContext } : extraContext
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
if (input == undefined) {
|
||||
@@ -116,7 +119,7 @@
|
||||
try {
|
||||
const r = await eval_like(
|
||||
input.expr,
|
||||
computeGlobalContext($worldStore, extraContext),
|
||||
computeGlobalContext($worldStore, fullContext),
|
||||
true,
|
||||
$state,
|
||||
$mode == 'dnd',
|
||||
@@ -138,7 +141,7 @@
|
||||
try {
|
||||
const r = await eval_like(
|
||||
'`' + input.eval + '`',
|
||||
computeGlobalContext($worldStore, extraContext),
|
||||
computeGlobalContext($worldStore, fullContext),
|
||||
true,
|
||||
$state,
|
||||
$mode == 'dnd',
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import { createEventDispatcher, getContext, onDestroy, onMount } from 'svelte'
|
||||
import type { AppInputs, Runnable } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppViewerContext, CancelablePromise, InlineScript } from '../../types'
|
||||
import type { AppViewerContext, CancelablePromise, InlineScript, ListContext } from '../../types'
|
||||
import { computeGlobalContext, eval_like } from './eval'
|
||||
import InputValue from './InputValue.svelte'
|
||||
import RefreshButton from './RefreshButton.svelte'
|
||||
@@ -57,6 +57,7 @@
|
||||
app,
|
||||
connectingInput
|
||||
} = getContext<AppViewerContext>('AppViewerContext')
|
||||
const iterContext = getContext<ListContext>('ListWrapperContext')
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -165,7 +166,7 @@
|
||||
try {
|
||||
const r = await eval_like(
|
||||
runnable.inlineScript?.content,
|
||||
computeGlobalContext($worldStore, {}),
|
||||
computeGlobalContext($worldStore, iterContext ? { iter: $iterContext } : {}),
|
||||
false,
|
||||
$state,
|
||||
$mode == 'dnd',
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentContainerHeight: number
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: RichConfigurations
|
||||
export let customCss: ComponentCustomCSS<'containercomponent'> | undefined = undefined
|
||||
@@ -41,6 +40,8 @@
|
||||
|
||||
$: css = concatCustomCss($app.css?.containercomponent, customCss)
|
||||
let result: any[] | undefined = undefined
|
||||
|
||||
$: isCard = resolvedConfig.width?.selected == 'card'
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['listcomponent'].initialData.configuration) as key (key)}
|
||||
@@ -53,33 +54,59 @@
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<RunnableWrapper {outputs} {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
|
||||
<div class="w-full h-full flex flex-wrap overflow-auto gap-2">
|
||||
<div
|
||||
class="w-full flex flex-wrap overflow-auto {isCard ? 'h-full gap-2' : 'divide-y max-h-full'}"
|
||||
>
|
||||
{#if $app.subgrids?.[`${id}-0`]}
|
||||
{#each result ?? [] as value, index}
|
||||
<div
|
||||
style={`min-width: ${resolvedConfig.minWidthPx}px; max-height: ${resolvedConfig.maxHeightPx}px;`}
|
||||
class="border overflow-auto"
|
||||
>
|
||||
<ListWrapper {value} {index}>
|
||||
<SubGridEditor
|
||||
visible={render}
|
||||
{id}
|
||||
class={css?.container?.class}
|
||||
style={css?.container?.style}
|
||||
subGridId={`${id}-0`}
|
||||
containerHeight={componentContainerHeight}
|
||||
forceView={index != 0}
|
||||
on:focus={() => {
|
||||
if (!$connectingInput.opened) {
|
||||
$selectedComponent = [id]
|
||||
}
|
||||
onFocus()
|
||||
}}
|
||||
/>
|
||||
</ListWrapper>
|
||||
</div>
|
||||
{/each}
|
||||
{#if Array.isArray(result)}
|
||||
{#each result ?? [] as value, index}
|
||||
<div
|
||||
style={`${
|
||||
isCard
|
||||
? `min-width: ${resolvedConfig.width?.configuration?.card?.minWidthPx}px; `
|
||||
: ''
|
||||
} max-height: ${resolvedConfig.heightPx}px;`}
|
||||
class="overflow-auto {!isCard ? 'w-full' : 'border'}"
|
||||
>
|
||||
<ListWrapper {value} {index}>
|
||||
<SubGridEditor
|
||||
visible={render}
|
||||
{id}
|
||||
class={css?.container?.class}
|
||||
style={css?.container?.style}
|
||||
subGridId={`${id}-0`}
|
||||
containerHeight={resolvedConfig.heightPx}
|
||||
on:focus={() => {
|
||||
if (!$connectingInput.opened) {
|
||||
$selectedComponent = [id]
|
||||
}
|
||||
onFocus()
|
||||
}}
|
||||
/>
|
||||
</ListWrapper>
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
<ListWrapper value={undefined} index={0}>
|
||||
<SubGridEditor
|
||||
visible={false}
|
||||
{id}
|
||||
class={css?.container?.class}
|
||||
style={css?.container?.style}
|
||||
subGridId={`${id}-0`}
|
||||
containerHeight={resolvedConfig.heightPx}
|
||||
on:focus={() => {
|
||||
if (!$connectingInput.opened) {
|
||||
$selectedComponent = [id]
|
||||
}
|
||||
onFocus()
|
||||
}}
|
||||
/>
|
||||
</ListWrapper>
|
||||
<div class="text-center text-gray-500">Input data is not an array</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</RunnableWrapper>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { setContext } from 'svelte'
|
||||
import type { ListContext } from '../../types'
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
export let index: number
|
||||
export let value: any
|
||||
|
||||
setContext<ListContext>('ListWrapperContext', { index, value })
|
||||
const ctx = writable({ index, value })
|
||||
|
||||
$: $ctx = { index, value }
|
||||
setContext<ListContext>('ListWrapperContext', ctx)
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
if (befSelected) {
|
||||
if (!['ctx', 'state'].includes(befSelected) && !befSelected?.startsWith(BG_PREFIX)) {
|
||||
let item = findGridItem($appStore, befSelected)
|
||||
if (item?.data.type === 'containercomponent') {
|
||||
if (item?.data.type === 'containercomponent' || item?.data.type === 'listcomponent') {
|
||||
$focusedGrid = {
|
||||
parentComponentId: befSelected,
|
||||
subGridIndex: 0
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
export let visible: boolean = true
|
||||
export let id: string
|
||||
export let shouldHighlight: boolean = true
|
||||
export let forceView = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -76,9 +75,9 @@
|
||||
classes ?? '',
|
||||
noPadding ? 'px-0' : 'px-2'
|
||||
)}
|
||||
style="{containerHeight ? `height: ${containerHeight}px;` : ''} {style ?? ''}"
|
||||
style="{containerHeight ? `height: ${containerHeight - 2}px;` : ''} {style ?? ''}"
|
||||
>
|
||||
{#if $mode !== 'preview' || forceView}
|
||||
{#if $mode !== 'preview'}
|
||||
<div
|
||||
class={highlight ? 'border-gray-600 animate-border border-dashed border-2 min-h-full' : ''}
|
||||
>
|
||||
|
||||
@@ -460,7 +460,6 @@
|
||||
customCss={component.customCss}
|
||||
configuration={component.configuration}
|
||||
componentInput={component.componentInput}
|
||||
{componentContainerHeight}
|
||||
{render}
|
||||
bind:initializing
|
||||
/>
|
||||
|
||||
@@ -475,24 +475,37 @@ export const components = {
|
||||
listcomponent: {
|
||||
name: 'List',
|
||||
icon: ListIcon,
|
||||
dims: '2:8-6:8' as AppComponentDimensions,
|
||||
dims: '3:8-12:8' as AppComponentDimensions,
|
||||
|
||||
customCss: {
|
||||
container: { class: '', style: '' }
|
||||
},
|
||||
initialData: {
|
||||
configuration: {
|
||||
minWidthPx: {
|
||||
width: {
|
||||
type: 'oneOf',
|
||||
selected: 'card',
|
||||
labels: {
|
||||
card: 'Card',
|
||||
row: 'Full Row'
|
||||
},
|
||||
configuration: {
|
||||
card: {
|
||||
minWidthPx: {
|
||||
type: 'static',
|
||||
fieldType: 'number',
|
||||
value: 300,
|
||||
tooltip: 'Min Width in pixels'
|
||||
}
|
||||
},
|
||||
row: {}
|
||||
}
|
||||
} as const,
|
||||
heightPx: {
|
||||
type: 'static',
|
||||
fieldType: 'number',
|
||||
value: 300,
|
||||
tooltip: 'Min Width in pixels'
|
||||
},
|
||||
maxHeightPx: {
|
||||
type: 'static',
|
||||
fieldType: 'number',
|
||||
value: 300,
|
||||
tooltip: 'Max Height in pixels'
|
||||
value: 280,
|
||||
tooltip: 'Height in pixels'
|
||||
}
|
||||
},
|
||||
componentInput: {
|
||||
|
||||
@@ -10,7 +10,7 @@ const layout: ComponentSet = {
|
||||
title: 'Layout',
|
||||
components: [
|
||||
'containercomponent',
|
||||
// 'listcomponent',
|
||||
'listcomponent',
|
||||
'horizontaldividercomponent',
|
||||
'verticaldividercomponent',
|
||||
'drawercomponent',
|
||||
|
||||
@@ -659,6 +659,9 @@ export const quickStyleProperties: Record<
|
||||
containercomponent: {
|
||||
container: containerDefaultProps
|
||||
},
|
||||
listcomponent: {
|
||||
container: containerDefaultProps
|
||||
},
|
||||
dateinputcomponent: {
|
||||
input: inputDefaultProps
|
||||
},
|
||||
|
||||
+2
-2
@@ -16,8 +16,8 @@
|
||||
<ScriptSettingsSection title="Triggers">
|
||||
<div class="flex flex-col">
|
||||
{#if autoRefresh !== undefined && canConfigureRunOnStart}
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="flex flex-row items-center gap-2 text-xs">
|
||||
<div class="flex items-center justify-between w-full gap-1">
|
||||
<div class="flex flex-row items-center gap-2 text-xs mb-0.5">
|
||||
Run on start and app refresh
|
||||
<Tooltip>
|
||||
You may want to disable this so that the background runnable is only triggered by
|
||||
|
||||
@@ -150,10 +150,10 @@ export interface CancelablePromise<T> extends Promise<T> {
|
||||
cancel: () => void
|
||||
}
|
||||
|
||||
export type ListContext = {
|
||||
export type ListContext = Writable<{
|
||||
index: number
|
||||
value: any
|
||||
}
|
||||
}>
|
||||
|
||||
export type AppViewerContext = {
|
||||
worldStore: Writable<World>
|
||||
|
||||
@@ -222,6 +222,7 @@ declare function setSelectedIndex(id: string, index: number): void;
|
||||
: ''
|
||||
}
|
||||
declare const state: ${JSON.stringify(state)};
|
||||
declare const iter: {index: number, value: any};
|
||||
`
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user