Files
windmill/frontend/src/lib/components/raw_apps/RawAppInlineScriptPanelList.svelte
T
Guilhem 13380fabaa fix(frontend): update raw app editor to brand guidelines (#7545)
* nit

* Update sidebar to brand guidelines

* use dropdown for file delete and rename

* update runnable script styling

* fix icon sizes

* Polish app creation form

* Add missing languages icons

* Allow  to delete runnable from the left pannel

* update top bar to brand guidelines

* improve svelte template

* fix runnables binding

* nit

* Deselect frontend file when selecting background runnable

* fix renaming wip

* fix add ing files, update only after adding

* Change filename when already existing

* update deployment page to brand guidelines

* fix folder open error

* expand folder if adding file inside

* nit

* fix bad link

* use alert for ai not configured message
2026-01-13 16:33:32 +00:00

165 lines
4.9 KiB
Svelte

<script lang="ts">
import { Button } from '$lib/components/common'
import { Plus, File, AlertCircle, AlertTriangle } from 'lucide-svelte'
import PanelSection from '../apps/editor/settingsPanel/common/PanelSection.svelte'
import Popover from '../Popover.svelte'
import type { Runnable } from '../apps/inputType'
import { getNextId } from '$lib/components/flows/idUtils'
import { rawAppLintStore } from './lintStore'
import RunnableRow from './RunnableRow.svelte'
interface Props {
selectedRunnable: string | undefined
runnables: Record<string, Runnable>
onSelect?: (id: string) => void
}
let { selectedRunnable = $bindable(), runnables, onSelect }: Props = $props()
// Subscribe to lint store for reactive updates
let lintSnapshot = $state(rawAppLintStore.getSnapshot())
$effect(() => {
const unsubscribe = rawAppLintStore.subscribe((s) => {
lintSnapshot = s
})
return unsubscribe
})
const errorCount = $derived(
Object.values(lintSnapshot.errors).reduce((acc, arr) => acc + arr.length, 0)
)
const warningCount = $derived(
Object.values(lintSnapshot.warnings).reduce((acc, arr) => acc + arr.length, 0)
)
function createBackgroundScript() {
const nid = getNextId(Object.keys(runnables ?? {}))
const newScriptPath = `Backend Runnable ${nid}`
runnables[nid] = {
name: newScriptPath,
inlineScript: undefined,
type: 'inline'
}
selectedRunnable = nid
onSelect?.(nid)
}
</script>
<PanelSection size="sm" fullHeight={false} title="backend" id="app-editor-runnable-panel">
{#snippet action()}
<div class="flex flex-row gap-1">
<Button
unifiedSize="xs"
variant="subtle"
title="Create a new background runnable"
aria-label="Create a new background runnable"
on:click={createBackgroundScript}
id="create-background-runnable"
btnClasses="gap-0.5 px-1"
>
<Plus size={12} />
<File size={12} />
</Button>
</div>
{/snippet}
<div class="w-full flex flex-col gap-6 py-1">
<div>
<div class="flex flex-col gap-1 w-full">
{#if Object.keys(runnables ?? {}).length > 0}
{#each Object.entries(runnables ?? {}) as [id, runnable]}
{#if runnable}
<RunnableRow
{id}
{runnable}
isSelected={selectedRunnable === id}
onSelect={() => {
selectedRunnable = id
onSelect?.(id)
}}
onDelete={() => {
delete runnables[id]
if (selectedRunnable === id) {
selectedRunnable = undefined
}
}}
/>
{/if}
{/each}
{:else}
<div class="text-xs text-primary">No backend runnable</div>
{/if}
</div>
{#if errorCount > 0 || warningCount > 0}
<Popover notClickable placement="right">
<div
class="flex items-center gap-2 px-2 py-1.5 mt-2 rounded border text-xs
{errorCount > 0
? 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800'
: 'bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800'}"
>
{#if errorCount > 0}
<span class="flex items-center gap-1 text-red-600 dark:text-red-400">
<AlertCircle size={14} />
{errorCount} error{errorCount !== 1 ? 's' : ''}
</span>
{/if}
{#if warningCount > 0}
<span class="flex items-center gap-1 text-yellow-600 dark:text-yellow-400">
<AlertTriangle size={14} />
{warningCount} warning{warningCount !== 1 ? 's' : ''}
</span>
{/if}
</div>
{#snippet text()}
<div class="max-w-md max-h-80 overflow-auto text-xs">
{#if errorCount > 0}
<div class="mb-2">
<div class="font-semibold text-red-600 dark:text-red-400 mb-1">Errors</div>
{#each Object.entries(lintSnapshot.errors) as [key, errors]}
{#if errors.length > 0}
<div class="mb-1">
<span class="font-medium text-secondary">{key}:</span>
<ul class="ml-2 list-disc list-inside">
{#each errors as error}
<li class="text-tertiary">
Line {error.startLineNumber}: {error.message}
</li>
{/each}
</ul>
</div>
{/if}
{/each}
</div>
{/if}
{#if warningCount > 0}
<div>
<div class="font-semibold text-yellow-600 dark:text-yellow-400 mb-1">Warnings</div
>
{#each Object.entries(lintSnapshot.warnings) as [key, warnings]}
{#if warnings.length > 0}
<div class="mb-1">
<span class="font-medium text-secondary">{key}:</span>
<ul class="ml-2 list-disc list-inside">
{#each warnings as warning}
<li class="text-tertiary">
Line {warning.startLineNumber}: {warning.message}
</li>
{/each}
</ul>
</div>
{/if}
{/each}
</div>
{/if}
</div>
{/snippet}
</Popover>
{/if}
</div>
</div>
</PanelSection>