mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 00:02:19 +00:00
App small fixes (#1258)
* fix(frontend): Fix runnable editor * fix(frontend): remove isopenstore * fix(frontend): add output searchbar * fix(frontend): fix build * fix(frontend): add missing clear button
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
<Popover notClickable {placement} class={wrapperClass}>
|
||||
<Icon
|
||||
class="{light
|
||||
? 'text-gray-300'
|
||||
? 'text-gray-400'
|
||||
: ' text-gray-500'} font-thin inline-block align-middle w-4 {$$props.class}"
|
||||
data={faInfoCircle}
|
||||
{scale}
|
||||
|
||||
@@ -201,20 +201,20 @@
|
||||
</Pane>
|
||||
<Pane size={21} minSize={5} maxSize={33}>
|
||||
<div class="relative flex flex-col h-full">
|
||||
<Tabs bind:selected={selectedTab} class="!border-b-2 !border-gray-200">
|
||||
<Tab value="insert" size="xs" class="grow">
|
||||
<Tabs bind:selected={selectedTab} class="!border-b-2 !border-gray-200 !h-full">
|
||||
<Tab value="insert" size="xs">
|
||||
<div class="m-1 center-center gap-2">
|
||||
<Icon data={faPlus} />
|
||||
<span>Insert</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab value="settings" size="xs" class="grow">
|
||||
<Tab value="settings" size="xs">
|
||||
<div class="m-1 center-center gap-2">
|
||||
<Icon data={faSliders} />
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab value="css" size="xs" class="grow">
|
||||
<Tab value="css" size="xs">
|
||||
<div class="m-1 center-center gap-2">
|
||||
<Icon data={faCode} />
|
||||
<span>CSS</span>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<script lang="ts">
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import { isOpenStore } from './store'
|
||||
import { getContext } from 'svelte'
|
||||
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
|
||||
import { components as componentsRecord, COMPONENT_SETS, type AppComponent } from '../component'
|
||||
import ListItem from './ListItem.svelte'
|
||||
import { insertNewGridItem } from '../appUtils'
|
||||
import { X } from 'lucide-svelte'
|
||||
|
||||
const TITLE_PREFIX = 'Component.' as const
|
||||
const { app, selectedComponent, focusedGrid } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function addComponent(appComponentType: AppComponent['type']): void {
|
||||
@@ -19,31 +18,62 @@
|
||||
$selectedComponent = id
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
isOpenStore.addItems(COMPONENT_SETS.map((set) => ({ [TITLE_PREFIX + set.title]: true })))
|
||||
})
|
||||
let search = ''
|
||||
|
||||
// Filter COMPONENT_SETS by search
|
||||
$: componentsFiltered = COMPONENT_SETS.map((set) => ({
|
||||
...set,
|
||||
components: set.components.filter((component) => {
|
||||
const name = componentsRecord[component].name.toLowerCase()
|
||||
return name.includes(search.toLowerCase())
|
||||
})
|
||||
}))
|
||||
</script>
|
||||
|
||||
{#each COMPONENT_SETS as { title, components }, index (index)}
|
||||
<ListItem {title} prefix={TITLE_PREFIX}>
|
||||
{#if components.length}
|
||||
<div class="flex flex-wrap gap-2 py-2">
|
||||
{#each components as item}
|
||||
<button
|
||||
on:click={() => addComponent(item)}
|
||||
title={componentsRecord[item].name}
|
||||
class="border w-24 shadow-sm h-16 p-2 flex flex-col gap-2 items-center
|
||||
justify-center bg-white rounded-md hover:bg-gray-100 duration-200"
|
||||
>
|
||||
<svelte:component this={componentsRecord[item].icon} />
|
||||
<div class="text-xs w-full text-center ellipsize">
|
||||
{componentsRecord[item].name}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-xs text-gray-500 py-1 px-2"> There are no components in this group yet </div>
|
||||
<section class="p-2 sticky bg-white border-b w-full h-12 z-20 top-0">
|
||||
<div class="relative">
|
||||
<input
|
||||
bind:value={search}
|
||||
class="px-2 py-1 border border-gray-300 rounded-sm {search ? 'pr-8' : ''}"
|
||||
placeholder="Search components..."
|
||||
/>
|
||||
{#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}
|
||||
</ListItem>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{#if componentsFiltered.reduce((acc, { components }) => acc + components.length, 0) === 0}
|
||||
<div class="text-xs text-gray-500 py-1 px-2"> No components found </div>
|
||||
{:else}
|
||||
{#each componentsFiltered as { title, components }, index (index)}
|
||||
<ListItem title={`${title} (${components.length})`}>
|
||||
{#if components.length}
|
||||
<div class="flex flex-wrap gap-2 py-2">
|
||||
{#each components as item}
|
||||
<button
|
||||
on:click={() => addComponent(item)}
|
||||
title={componentsRecord[item].name}
|
||||
class="border w-24 shadow-sm h-16 p-2 flex flex-col gap-2 items-center
|
||||
justify-center bg-white rounded-md hover:bg-gray-100 duration-200"
|
||||
>
|
||||
<svelte:component this={componentsRecord[item].icon} />
|
||||
<div class="text-xs w-full text-center ellipsize">
|
||||
{componentsRecord[item].name}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-xs text-gray-500 py-1 px-2">
|
||||
There are no components in this group yet
|
||||
</div>
|
||||
{/if}
|
||||
</ListItem>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
@@ -4,27 +4,38 @@
|
||||
import { isOpenStore } from './store'
|
||||
|
||||
export let title: string
|
||||
export let prefix: string = ''
|
||||
export let prefix: string | undefined = undefined
|
||||
|
||||
$: storeTitle = prefix + title
|
||||
$: isOpen = $isOpenStore[storeTitle]
|
||||
$: isOpen = prefix ? $isOpenStore[storeTitle] : true
|
||||
</script>
|
||||
|
||||
<section class="pt-1 pb-2 px-1">
|
||||
<button
|
||||
on:click|preventDefault={() => isOpenStore.toggle(storeTitle)}
|
||||
class="w-full flex justify-between items-center text-gray-700 px-2 py-1
|
||||
{#if prefix !== undefined}
|
||||
<button
|
||||
on:click|preventDefault={() => isOpenStore.toggle(storeTitle)}
|
||||
class="w-full flex justify-between items-center text-gray-700 px-2 py-1
|
||||
rounded-sm duration-200 hover:bg-gray-100"
|
||||
>
|
||||
<h1 class="text-sm font-semibold text-left">
|
||||
>
|
||||
<h1 class="text-sm font-semibold text-left">
|
||||
<slot name="title">
|
||||
{title}
|
||||
</slot>
|
||||
</h1>
|
||||
<ChevronDown class="rotate-0 duration-300 {isOpen ? '!rotate-180' : ''}" />
|
||||
</button>
|
||||
{#if isOpen}
|
||||
<div transition:slide|local={{ duration: 300 }} class="px-2">
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<h1 class="text-sm font-semibold text-left px-2 py-1">
|
||||
<slot name="title">
|
||||
{title}
|
||||
</slot>
|
||||
</h1>
|
||||
<ChevronDown class="rotate-0 duration-300 {isOpen ? '!rotate-180' : ''}" />
|
||||
</button>
|
||||
{#if isOpen}
|
||||
<div transition:slide|local={{ duration: 300 }} class="px-2">
|
||||
<div class="px-2">
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
import { X } from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import { findGridItem } from '../appUtils'
|
||||
@@ -42,13 +43,40 @@
|
||||
$: panels = [['ctx', ['email', 'username', 'query']] 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]) => {
|
||||
const name = getComponentNameById(componentId)
|
||||
return (
|
||||
name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
componentId.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<PanelSection noPadding titlePadding="px-4 pt-2 pb-0.5" title="Outputs">
|
||||
<div
|
||||
class="overflow-auto min-w-[150px] border-t w-full relative flex flex-col gap-4 px-2 pt-4 pb-2"
|
||||
>
|
||||
{#each panels as [componentId, outputs] (componentId)}
|
||||
<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>
|
||||
{#each filteredPanels as [componentId, outputs] (componentId)}
|
||||
{#if outputs.length > 0 && $worldStore?.outputsById[componentId]}
|
||||
{@const name = getComponentNameById(componentId)}
|
||||
<div>
|
||||
|
||||
+12
-2
@@ -7,7 +7,7 @@
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import { initialCode } from '$lib/script_helpers'
|
||||
import { capitalize, emptySchema, getScriptByPath } from '$lib/utils'
|
||||
import { faCodeBranch } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faCodeBranch, faTrash } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Building, Globe2 } from 'lucide-svelte'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import { fly } from 'svelte/transition'
|
||||
@@ -176,12 +176,22 @@
|
||||
<div class="mt-4">
|
||||
<Button
|
||||
on:click={() => picker?.openDrawer()}
|
||||
size="sm"
|
||||
size="xs"
|
||||
color="blue"
|
||||
startIcon={{ icon: faCodeBranch }}
|
||||
btnClasses="truncate"
|
||||
>
|
||||
or fork a detached/Workspace/Hub script
|
||||
</Button>
|
||||
<Button
|
||||
on:click={() => dispatch('delete')}
|
||||
size="xs"
|
||||
color="red"
|
||||
variant="border"
|
||||
startIcon={{ icon: faTrash }}
|
||||
btnClasses="truncate"
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+40
-22
@@ -3,7 +3,7 @@
|
||||
import type { Preview } from '$lib/gen'
|
||||
import { createEventDispatcher, getContext, onMount } from 'svelte'
|
||||
import type { AppEditorContext, InlineScript } from '../../types'
|
||||
import { CheckCircle, Maximize2, Trash2, X } from 'lucide-svelte'
|
||||
import { CornerDownLeft, Maximize2, Plus, Trash2, X } from 'lucide-svelte'
|
||||
import InlineScriptEditorDrawer from './InlineScriptEditorDrawer.svelte'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import type { Schema } from '$lib/common'
|
||||
@@ -11,11 +11,11 @@
|
||||
import { fly } from 'svelte/transition'
|
||||
import Editor from '$lib/components/Editor.svelte'
|
||||
import { emptySchema, scriptLangToEditorLang } from '$lib/utils'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import Popover from '../../../Popover.svelte'
|
||||
import { computeFields } from './utils'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import Kbd from '$lib/components/common/kbd/Kbd.svelte'
|
||||
|
||||
let inlineScriptEditorDrawer: InlineScriptEditorDrawer
|
||||
|
||||
@@ -73,6 +73,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let isMac = navigator.userAgent.indexOf('Mac OS X') !== -1
|
||||
</script>
|
||||
|
||||
<InlineScriptEditorDrawer {editor} bind:this={inlineScriptEditorDrawer} bind:inlineScript />
|
||||
@@ -80,30 +82,25 @@
|
||||
<div class="h-full flex flex-col gap-1" transition:fly|local={{ duration: 50 }}>
|
||||
<div class="flex justify-between w-full gap-2 px-2 pt-1 flex-row items-center">
|
||||
{#if name !== undefined}
|
||||
<input bind:value={name} placeholder="Inline script name" />
|
||||
<input bind:value={name} placeholder="Inline script name" class="!text-xs !rounded-xs" />
|
||||
{/if}
|
||||
<div class="flex w-full flex-row gap-2 items-center justify-end">
|
||||
{#if validCode}
|
||||
<Badge color="green" baseClass="!p-0 !h-[30px] aspect-square center-center">
|
||||
<CheckCircle size={16} />
|
||||
</Badge>
|
||||
<Badge color="green" baseClass="!text-2xs">Valid</Badge>
|
||||
{:else}
|
||||
<Badge color="red" baseClass="!p-0 !h-[30px] aspect-square center-center">
|
||||
<X size={16} />
|
||||
</Badge>
|
||||
<Badge color="red" baseClass="!text-2xs">Invalid</Badge>
|
||||
{/if}
|
||||
|
||||
{#if id.startsWith('unused-') || id.startsWith('bg_')}
|
||||
<Popover notClickable placement="bottom">
|
||||
<Button
|
||||
size="xs"
|
||||
color="red"
|
||||
variant="border"
|
||||
btnClasses="!px-1.5"
|
||||
color="light"
|
||||
btnClasses="!px-2 !bg-red-100 hover:!bg-red-200"
|
||||
aria-label="Delete"
|
||||
on:click={() => dispatch('delete')}
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
<Trash2 size={14} class="text-red-800" />
|
||||
</Button>
|
||||
<svelte:fragment slot="text">Delete</svelte:fragment>
|
||||
</Popover>
|
||||
@@ -111,41 +108,62 @@
|
||||
<Popover notClickable placement="bottom">
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
variant="border"
|
||||
btnClasses="!px-1.5"
|
||||
color="light"
|
||||
btnClasses="!px-2 !bg-gray-100 hover:!bg-gray-200"
|
||||
aria-label="Open full editor"
|
||||
on:click={() => {
|
||||
inlineScriptEditorDrawer?.openDrawer()
|
||||
}}
|
||||
>
|
||||
<Maximize2 size={16} />
|
||||
<Maximize2 size={14} />
|
||||
</Button>
|
||||
<svelte:fragment slot="text">Open full editor</svelte:fragment>
|
||||
</Popover>
|
||||
<Button
|
||||
variant="border"
|
||||
size="xs"
|
||||
color="blue"
|
||||
color="light"
|
||||
btnClasses="!px-2 !py-1"
|
||||
on:click={async () => {
|
||||
editor.format()
|
||||
}}
|
||||
>
|
||||
Format <Tooltip placement="bottom">Ctrl+S</Tooltip>
|
||||
<div class="flex flex-row gap-1 items-center">
|
||||
Format
|
||||
|
||||
<div class="flex flex-row items-center">
|
||||
<Kbd>{isMac ? '⌘' : 'CTRL'}</Kbd>
|
||||
<Plus size={12} />
|
||||
<Kbd>S</Kbd>
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
{#if $runnableComponents[id] != undefined}
|
||||
<Button
|
||||
loading={runLoading}
|
||||
size="xs"
|
||||
color="blue"
|
||||
color="dark"
|
||||
variant="border"
|
||||
btnClasses="!px-2 !py-1 !bg-gray-700 !text-white hover:!bg-gray-900"
|
||||
on:click={async () => {
|
||||
runLoading = true
|
||||
await $runnableComponents[id]?.()
|
||||
runLoading = false
|
||||
}}
|
||||
>
|
||||
Run
|
||||
<Tooltip light placement="bottom">Ctrl+Enter</Tooltip>
|
||||
<div class="flex flex-row gap-1 items-center">
|
||||
Run
|
||||
|
||||
<div class="flex flex-row items-center">
|
||||
<Kbd>{isMac ? '⌘' : 'CTRL'}</Kbd>
|
||||
<Plus size={12} />
|
||||
<Kbd>
|
||||
<div class="h-4 flex items-center justify-center">
|
||||
<CornerDownLeft size={10} />
|
||||
</div>
|
||||
</Kbd>
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
+8
-5
@@ -62,6 +62,12 @@
|
||||
) {
|
||||
refreshScript(componentInput.runnable)
|
||||
}
|
||||
|
||||
function deleteInlineScript() {
|
||||
if (componentInput && componentInput.type == 'runnable') {
|
||||
componentInput = clearResultAppInput(componentInput)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={drawerFlowViewer} size="1200px">
|
||||
@@ -80,16 +86,13 @@
|
||||
bind:name={componentInput.runnable.name}
|
||||
bind:fields={componentInput.fields}
|
||||
syncFields
|
||||
on:delete={() => {
|
||||
if (componentInput && componentInput.type == 'runnable') {
|
||||
componentInput = clearResultAppInput(componentInput)
|
||||
}
|
||||
}}
|
||||
on:delete={deleteInlineScript}
|
||||
/>
|
||||
{:else}
|
||||
<EmptyInlineScript
|
||||
{id}
|
||||
name={componentInput.runnable.name}
|
||||
on:delete={deleteInlineScript}
|
||||
on:new={(e) => {
|
||||
if (
|
||||
componentInput &&
|
||||
|
||||
+12
-9
@@ -12,6 +12,16 @@
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let selectedScriptComponentId: string | undefined = undefined
|
||||
|
||||
function deleteBackgroundScript(index: number) {
|
||||
// remove the script from the array at the index
|
||||
$app.hiddenInlineScripts.splice(index, 1)
|
||||
$app.hiddenInlineScripts = [...$app.hiddenInlineScripts]
|
||||
|
||||
delete $staticOutputs[`bg_${index}`]
|
||||
delete $runnableComponents[`bg_${index}`]
|
||||
$staticOutputs = $staticOutputs
|
||||
}
|
||||
</script>
|
||||
|
||||
<SplitPanesWrapper>
|
||||
@@ -97,20 +107,13 @@
|
||||
bind:name={hiddenInlineScript.name}
|
||||
bind:fields={hiddenInlineScript.fields}
|
||||
syncFields
|
||||
on:delete={() => {
|
||||
// remove the script from the array at the index
|
||||
$app.hiddenInlineScripts.splice(index, 1)
|
||||
$app.hiddenInlineScripts = [...$app.hiddenInlineScripts]
|
||||
|
||||
delete $staticOutputs[`bg_${index}`]
|
||||
delete $runnableComponents[`bg_${index}`]
|
||||
$staticOutputs = $staticOutputs
|
||||
}}
|
||||
on:delete={() => deleteBackgroundScript(index)}
|
||||
/>
|
||||
{:else}
|
||||
<EmptyInlineScript
|
||||
id={`b_${index}`}
|
||||
name={hiddenInlineScript.name}
|
||||
on:delete={() => deleteBackgroundScript(index)}
|
||||
on:new={(e) => {
|
||||
hiddenInlineScript.inlineScript = e.detail
|
||||
}}
|
||||
|
||||
+8
-5
@@ -54,9 +54,9 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="sticky top-0 py-0.5 px-2 text-center bg-gray-200 text-gray-500 text-2xs font-bold tracking-wide border-b border-gray-300"
|
||||
class="sticky top-0 py-0.5 px-2 text-left bg-gray-50 text-gray-800 text-xs font-semibold tracking-wide border-b border-gray-300"
|
||||
>
|
||||
RUNNABLES
|
||||
Runnables
|
||||
</div>
|
||||
<div bind:this={list} class="grow flex flex-col gap-4">
|
||||
<PanelSection title="Inline scripts" smallPadding>
|
||||
@@ -129,9 +129,12 @@
|
||||
smallPadding
|
||||
>
|
||||
<svelte:fragment slot="action">
|
||||
<Button size="xs" color="dark" variant="border" on:click={createBackgroundScript}
|
||||
><Plus size={14} />
|
||||
</Button>
|
||||
<button
|
||||
class="rounded-full bg-gray-100 hover:bg-gray-200 p-1 border border-gray-200"
|
||||
on:click={createBackgroundScript}
|
||||
>
|
||||
<Plus size={14} class="text-gray-500" />
|
||||
</button>
|
||||
</svelte:fragment>
|
||||
<div class="flex flex-col gap-1 w-full">
|
||||
{#if $app.hiddenInlineScripts?.length > 0}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { faChevronDown, faChevronUp, faCopy, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext, GridItem } from '../../types'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import PanelSection from './common/PanelSection.svelte'
|
||||
import InputsSpecsEditor from './InputsSpecsEditor.svelte'
|
||||
import TableActions from './TableActions.svelte'
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
)}
|
||||
>
|
||||
<div class="flex justify-between flex-wrap items-center w-full gap-1">
|
||||
<div class="text-sm inline-flex items-center font-extrabold {titlePadding}">
|
||||
<div class="text-sm inline-flex items-center font-extrabold {titlePadding} gap-1">
|
||||
<span class="truncate">
|
||||
{title}
|
||||
</span>
|
||||
{#if tooltip}
|
||||
<Tooltip>
|
||||
<Tooltip light>
|
||||
{tooltip}
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user