Files
windmill/frontend/src/lib/components/ContentSearchInner.svelte

432 lines
11 KiB
Svelte

<script lang="ts">
import { AppService, FlowService, ResourceService, ScriptService } from '$lib/gen'
import { enterpriseLicense, workspaceStore } from '$lib/stores'
import {
ArrowDown,
Boxes,
Code2,
Edit,
ExternalLink,
LayoutDashboard,
Loader2
} from 'lucide-svelte'
import SearchItems from './SearchItems.svelte'
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
import FlowIcon from './home/FlowIcon.svelte'
import { Alert, Button } from './common'
import YAML from 'yaml'
import { twMerge } from 'tailwind-merge'
import ContentSearchInnerItem from './ContentSearchInnerItem.svelte'
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
export let search: string = ''
export async function open(nsearch?: string) {
await Promise.all([loadScripts(), loadResources(), loadApps(), loadFlows()])
if (nsearch) {
search = nsearch
}
}
export async function loadScripts() {
scripts = await ScriptService.listSearchScript({ workspace: $workspaceStore ?? '' })
}
export async function loadResources() {
resources = await ResourceService.listSearchResource({ workspace: $workspaceStore ?? '' })
}
export async function loadApps() {
apps = await AppService.listSearchApp({ workspace: $workspaceStore ?? '' })
}
export async function loadFlows() {
flows = await FlowService.listSearchFlow({ workspace: $workspaceStore ?? '' })
}
let searchKind: 'all' | 'scripts' | 'flows' | 'apps' | 'resources' = 'all'
let scripts: undefined | { path: string; content: string }[] = undefined
let filteredScriptItems: { path: string; content: string; marked: any }[] = []
let resources: undefined | { path: string; value: any }[] = undefined
let filteredResourceItems: { path: string; value: any; marked: any }[] = []
let flows: undefined | { path: string; value: any }[] = undefined
let filteredFlowItems: { path: string; value: any; marked: any }[] = []
let apps: undefined | { path: string; value: any }[] = undefined
let filteredAppItems: { path: string; value: any; marked: any }[] = []
function getCounts(n: number) {
return ` (${n})`
}
function escape(htmlStr) {
return htmlStr
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
}
$: counts =
search == '' ||
!scripts ||
!resources ||
!flows ||
!apps ||
!filteredAppItems ||
!filteredFlowItems ||
!filteredResourceItems ||
!filteredScriptItems
? {
all: '',
apps: '',
flows: '',
resources: '',
scripts: ''
}
: {
all: getCounts(
filteredAppItems.length +
filteredFlowItems.length +
filteredResourceItems.length +
filteredScriptItems.length
),
apps: getCounts(filteredAppItems.length),
resources: getCounts(filteredResourceItems.length),
flows: getCounts(filteredFlowItems.length),
scripts: getCounts(filteredScriptItems.length)
}
let showNbScripts = 10
let showNbApps = 10
let showNbResources = 10
let showNbFlows = 10
$: search && resetShows()
function resetShows() {
showNbScripts = 10
showNbApps = 10
showNbResources = 10
showNbFlows = 10
}
</script>
<SearchItems
filter={search}
items={scripts}
f={(s) => {
return escape(s.content)
}}
bind:filteredItems={filteredScriptItems}
/>
<SearchItems
filter={search}
items={resources}
f={(s) => {
return escape(YAML.stringify(s.value))
}}
bind:filteredItems={filteredResourceItems}
/>
<SearchItems
filter={search}
items={flows}
f={(s) => {
return escape(YAML.stringify(s.value, null, 4))
}}
bind:filteredItems={filteredFlowItems}
/>
<SearchItems
filter={search}
items={apps}
f={(s) => {
return escape(YAML.stringify(s.value, null, 4))
}}
bind:filteredItems={filteredAppItems}
/>
<div class="flex flex-col gap-2">
<div class="flex gap-2 flex-wrap sticky top-0 left-0 right-0 bg-surface">
<div class="p-2">
<ToggleButtonGroup bind:selected={searchKind} class="h-10 ">
<ToggleButton small light value="all" label={'All' + counts.all} />
<ToggleButton small light value="scripts" icon={Code2} label={'Scripts' + counts.scripts} />
<ToggleButton
small
light
value="resources"
icon={Boxes}
label={'Resources' + counts.resources}
/>
<ToggleButton
small
light
value="flows"
label={'Flows' + counts.flows}
icon={FlowIcon}
selectedColor="#14b8a6"
/>
<ToggleButton
small
light
value="apps"
label={'Apps' + counts.apps}
icon={LayoutDashboard}
selectedColor="#fb923c"
/>
</ToggleButtonGroup>
</div>
</div>
<div class="px-2">
<div class="text-xs text-secondary">
Searching among <div class="inline-flex">
{#if scripts}{scripts?.length}{:else}
<Loader2 size={10} class="animate-spin " />
{/if}
</div>
scripts,
<div class="inline-flex">
{#if resources}{resources?.length}{:else}
<Loader2 size={10} class="animate-spin " />
{/if}
</div>
resources,
<div class="inline-flex">
{#if flows}{flows?.length}{:else}
<Loader2 size={10} class="animate-spin " />
{/if}
</div>
flows,
<div class="inline-flex">
{#if apps}{apps?.length}{:else}
<Loader2 size={10} class="animate-spin " />
{/if}
</div>
apps
</div>
</div>
<div class={twMerge('p-2')}>
{#if !$enterpriseLicense}
<div class="py-1" />
<Alert title="Content Search is an EE feature" type="warning">
Without EE, content search will only search among 10 scripts, 3 flows, 3 apps and 3
resources.
</Alert>
<div class="py-1" />
{/if}
{#if search.trim().length > 0}
<div class="flex flex-col gap-4">
{#if (searchKind == 'all' || searchKind == 'scripts') && filteredScriptItems?.length > 0}
{#each filteredScriptItems.slice(0, showNbScripts) ?? [] as item}
<ContentSearchInnerItem
title={`Script: ${item.path}`}
href={`/scripts/get/${item.path}`}
on:close
>
<svelte:fragment slot="actions">
<Button
href={`/scripts/get/${item.path}`}
color="light"
size="xs"
startIcon={{ icon: ExternalLink }}
on:click={() => {
dispatch('close')
}}
>
Open
</Button>
<Button
href={`/scripts/edit/${item.path}?no_draft=true`}
target="_blank"
color="light"
size="xs"
startIcon={{ icon: Edit }}
>
Edit
</Button>
</svelte:fragment>
<pre class="text-xs border rounded-md p-2 overflow-auto max-h-40 w-full"
><code>{@html item.marked}</code>
</pre>
</ContentSearchInnerItem>
{/each}
{#if filteredScriptItems.length > showNbScripts}
<Button
color="light"
size="xs"
on:click={() => {
showNbScripts += 30
}}
startIcon={{
icon: ArrowDown
}}
>
Show more scripts ({showNbScripts} of {filteredScriptItems.length})
</Button>
{/if}
{/if}
{#if (searchKind == 'all' || searchKind == 'resources') && filteredResourceItems?.length > 0}
{#each filteredResourceItems.slice(0, showNbResources) ?? [] as item}
<ContentSearchInnerItem
title={`Resource: ${item.path}`}
href={`/resources#${item.path}`}
on:close
>
<svelte:fragment slot="actions">
<Button
href={`/resources#${item.path}`}
color="light"
target="_blank"
size="xs"
startIcon={{ icon: Edit }}
>
Edit
</Button>
</svelte:fragment>
<pre class="text-xs border rounded-md p-2 overflow-auto max-h-40 w-full"
><code>{@html item.marked}</code></pre
>
</ContentSearchInnerItem>
{/each}
{#if filteredResourceItems.length > showNbResources}
<Button
color="light"
size="xs"
on:click={() => {
showNbResources += 30
}}
startIcon={{
icon: ArrowDown
}}
>
Show more resources ({showNbResources} of {filteredResourceItems.length})
</Button>
{/if}
{/if}
{#if (searchKind == 'all' || searchKind == 'flows') && filteredFlowItems?.length > 0}
{#each filteredFlowItems.slice(0, showNbFlows) ?? [] as item}
<ContentSearchInnerItem
title={`Flow: ${item.path}`}
href={`/flows/get/${item.path}`}
on:close
>
<svelte:fragment slot="actions">
<Button
href={`/flows/get/${item.path}`}
color="light"
size="xs"
startIcon={{ icon: ExternalLink }}
on:click={() => {
dispatch('close')
}}
>
Open
</Button>
<Button
href={`/flows/edit/${item.path}?no_draft=true`}
color="light"
target="_blank"
size="xs"
startIcon={{ icon: Edit }}
>
Edit
</Button>
</svelte:fragment>
<pre class="text-xs border p-2 overflow-auto max-h-40 w-full"
><code>{@html item.marked}</code></pre
>
</ContentSearchInnerItem>
{/each}
{#if filteredFlowItems.length > showNbFlows}
<Button
color="light"
size="xs"
on:click={() => {
showNbFlows += 30
}}
startIcon={{
icon: ArrowDown
}}
>
Show more flows ({showNbFlows} of {filteredFlowItems.length})
</Button>
{/if}
{/if}
{#if (searchKind == 'all' || searchKind == 'apps') && filteredAppItems?.length > 0}
{#each filteredAppItems.slice(0, showNbApps) ?? [] as item}
<ContentSearchInnerItem
title={`App: ${item.path}`}
href={`/apps/get/${item.path}`}
on:close
>
<svelte:fragment slot="actions">
<Button
href={`/apps/get/${item.path}`}
color="light"
size="xs"
startIcon={{ icon: ExternalLink }}
on:click={() => {
dispatch('close')
}}
>
Open
</Button>
<Button
href={`/apps/edit/${item.path}?no_draft=true`}
color="light"
target="_blank"
size="xs"
startIcon={{ icon: Edit }}
>
Edit
</Button>
</svelte:fragment>
<pre class="text-xs border p-2 overflow-auto max-h-40 w-full"
><code>{@html item.marked}</code></pre
>
</ContentSearchInnerItem>
{/each}
{#if filteredAppItems.length > showNbApps}
<Button
color="light"
size="xs"
on:click={() => {
showNbApps += 30
}}
startIcon={{
icon: ArrowDown
}}
>
Show more apps ({showNbApps} of {filteredAppItems.length})
</Button>
{/if}
{/if}
</div>
{:else}
<div class="flex justify-center items-center h-48">
<div class="text-tertiary text-center">
<div class="text-2xl font-bold">Empty Search Filter</div>
<div class="text-sm"
>Start writing, search everywhere a path is referenced for instance</div
>
</div>
</div>
{/if}
</div>
</div>