improve search windows and remove fuse.js

This commit is contained in:
Ruben Fiszel
2022-12-04 11:55:26 +01:00
parent df27e8252a
commit 2fbc8a39b3
20 changed files with 243 additions and 252 deletions

View File

@@ -20,7 +20,6 @@
"chartjs-adapter-date-fns": "^2.0.0",
"chartjs-plugin-zoom": "^1.2.1",
"date-fns": "^2.29.3",
"fuse.js": "^6.4.6",
"highlight.js": "^11.5.1",
"monaco-editor": "^0.34.1",
"monaco-editor-workers": "^0.34.2",
@@ -3070,14 +3069,6 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"node_modules/fuse.js": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz",
"integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==",
"engines": {
"node": ">=10"
}
},
"node_modules/get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
@@ -9131,11 +9122,6 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"fuse.js": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz",
"integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA=="
},
"get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",

View File

@@ -71,7 +71,6 @@
"chartjs-adapter-date-fns": "^2.0.0",
"chartjs-plugin-zoom": "^1.2.1",
"date-fns": "^2.29.3",
"fuse.js": "^6.4.6",
"highlight.js": "^11.5.1",
"monaco-editor": "^0.34.1",
"monaco-editor-workers": "^0.34.2",

View File

@@ -3,19 +3,19 @@
</script>
<script lang="ts">
import { ResourceService, ScriptService, VariableService } from '$lib/gen'
import { getScriptByPath, loadHubScripts, sendUserToast } from '$lib/utils'
import { ResourceService, VariableService } from '$lib/gen'
import { getScriptByPath, sendUserToast } from '$lib/utils'
import {
faBuilding,
faCube,
faDollarSign,
faEye,
faRotate,
faRotateLeft,
faWallet
faRotateLeft
} from '@fortawesome/free-solid-svg-icons'
import { hubScripts, workspaceStore } from '$lib/stores'
import { workspaceStore } from '$lib/stores'
import type Editor from './Editor.svelte'
import ItemPicker from './ItemPicker.svelte'
import ResourceEditor from './ResourceEditor.svelte'
@@ -24,23 +24,27 @@
import HighlightCode from './HighlightCode.svelte'
import DrawerContent from './common/drawer/DrawerContent.svelte'
import { Badge, Drawer } from './common'
import WorkspaceScriptPicker from './flows/pickers/WorkspaceScriptPicker.svelte'
import PickHubScript from './flows/pickers/PickHubScript.svelte'
import ToggleHubWorkspace from './ToggleHubWorkspace.svelte'
import Skeleton from './common/skeleton/Skeleton.svelte'
export let lang: 'python3' | 'deno' | 'go' | 'bash'
export let editor: Editor
export let websocketAlive: { pyright: boolean; black: boolean; deno: boolean; go: boolean }
export let iconOnly: boolean = false
export let validCode: boolean = true
export let kind: 'script' | 'trigger' | 'approval' = 'script'
let contextualVariablePicker: ItemPicker
let variablePicker: ItemPicker
let resourcePicker: ItemPicker
let scriptPicker: ItemPicker
let variableEditor: VariableEditor
let resourceEditor: ResourceEditor
let codeViewer: Drawer
let codeLang: 'python3' | 'deno' | 'go' | 'bash' = 'deno'
let codeContent: string = ''
let codeObj: { language: 'python3' | 'deno' | 'go' | 'bash'; content: string } | undefined =
undefined
function addEditorActions() {
editor.addAction('insert-variable', 'Windmill: Insert variable', () => {
@@ -63,36 +67,38 @@
})
}
async function loadScripts(): Promise<{ path: string; summary?: string }[]> {
const workspaceScripts: { path: string; summary?: string }[] = await ScriptService.listScripts({
workspace: $workspaceStore ?? 'NO_W'
})
if (!$hubScripts) {
await loadHubScripts()
}
const hubScripts_ = $hubScripts ?? []
let scriptPicker: Drawer
let pick_existing: 'hub' | 'workspace' = 'hub'
let filter = ''
return workspaceScripts.concat(hubScripts_)
async function onScriptPick(e: { detail: { path: string } }) {
codeObj = undefined
codeViewer?.openDrawer?.()
codeObj = await getScriptByPath(e.detail.path ?? '')
}
</script>
<ItemPicker
bind:this={scriptPicker}
pickCallback={async (path, _) => {
const { language, content } = await getScriptByPath(path ?? '')
codeContent = content
codeLang = language
codeViewer.openDrawer()
}}
closeOnClick={false}
itemName="script"
extraField="summary"
loadItems={loadScripts}
/>
<Drawer bind:this={scriptPicker} size="900px">
<DrawerContent title="Code" on:close={scriptPicker.closeDrawer}>
{#if pick_existing == 'hub'}
<PickHubScript bind:filter {kind} on:pick={onScriptPick}>
<ToggleHubWorkspace bind:selected={pick_existing} /></PickHubScript
>
{:else}
<WorkspaceScriptPicker bind:filter {kind} on:pick={onScriptPick}
><ToggleHubWorkspace bind:selected={pick_existing} /></WorkspaceScriptPicker
>
{/if}
</DrawerContent>
</Drawer>
<Drawer bind:this={codeViewer} size="600px">
<DrawerContent title="Code" on:close={codeViewer.closeDrawer}>
<HighlightCode language={codeLang} code={codeContent} />
{#if codeObj}
<HighlightCode language={codeObj?.language} code={codeObj?.content} />
{:else}
<Skeleton layout={[[40]]} />
{/if}
</DrawerContent>
</Drawer>
@@ -197,6 +203,7 @@
itemName="Resource"
buttons={{ 'Edit/View': (x) => resourceEditor.initEdit(x) }}
extraField="description"
extraField2="resource_type"
loadItems={async () =>
await ResourceService.listResource({ workspace: $workspaceStore ?? 'NO_W' })}
>

View File

@@ -1,13 +1,14 @@
<script lang="ts">
import Fuse from 'fuse.js'
import { Drawer, Skeleton } from './common'
import DrawerContent from './common/drawer/DrawerContent.svelte'
import IconedResourceType from './IconedResourceType.svelte'
import SearchItems from './SearchItems.svelte'
type Item = Record<string, any>
export let pickCallback: (path: string, f: string) => void
export let loadItems: () => Promise<Item[] | undefined>
export let extraField: string
export let extraField: string = 'path'
export let extraField2: string | undefined
export let itemName: string
export let closeOnClick = true
/** Displayed if the load function returns no items. */
@@ -19,32 +20,33 @@
let loading = false
let items: Item[] | undefined = []
let filteredItems: Item[] | undefined = []
let itemsFilter = ''
const fuseOptions = {
includeScore: false,
keys: ['path', extraField]
}
const fuse: Fuse<Item> = new Fuse(items, fuseOptions)
let filter = ''
export function openDrawer() {
loading = true
loadItems().then((v) => {
items = v
if (items) {
fuse.setCollection(items)
}
loading = false
})
drawer.openDrawer()
drawer.openDrawer?.()
}
$: filteredItems =
itemsFilter.length > 0 && items ? fuse.search(itemsFilter).map((value) => value.item) : items
let drawer: Drawer
</script>
<SearchItems
{filter}
{items}
bind:filteredItems
f={(x) =>
(extraField2 ? x[extraField2] + ' ' : '') +
(x[extraField] ?? '') +
' ' +
(x['path'] && x['path'] != x[extraField] ? '(' + x['path'] + ')' ?? '' : '') +
' ' +
(x['description'] != x[extraField] ? x['description'] ?? '' : '')}
/>
<Drawer bind:this={drawer} size="600px">
<DrawerContent title="Search a {itemName}" on:close={drawer.closeDrawer}>
<div>
@@ -52,7 +54,7 @@
<input
type="text"
placeholder="Search {itemName}"
bind:value={itemsFilter}
bind:value={filter}
class="search-item"
/>
</div>
@@ -89,17 +91,24 @@
</div>
{/if}
<div class="flex flex-col">
<div class="text-sm font-semibold flex flex-col">
<span class="mr-2 text-left">{obj[extraField] ?? ''}</span>
{#if extraField != 'path'}
<span class="font-normal text-xs text-left italic overflow-hidden"
>{obj['path'] ?? ''}</span
{#if obj.marked}
<div class="text-sm font-semibold">
{@html obj.marked}
</div>
{:else}
<div class="text-sm font-semibold flex flex-col">
<span class="mr-2 text-left">{obj[extraField] ?? ''}</span>
{#if extraField != 'path'}
<span class="font-normal text-xs text-left italic overflow-hidden"
>{obj['path'] ?? ''}</span
>
{/if}
</div>
{#if extraField != 'description'}
<div class="text-xs font-light italic text-left"
>{obj['description'] ?? ''}</div
>
{/if}
</div>
{#if extraField != 'description'}
<div class="text-xs font-light italic text-left">{obj['description'] ?? ''}</div
>
{/if}
</div>
</button>

View File

@@ -260,6 +260,7 @@
/>{/if}
<div class="border rounded p-2">
<SimpleEditor
fixedOverflowWidgets={false}
on:change={() => {
try {
schema = JSON.parse(schemaString)

View File

@@ -290,6 +290,7 @@
bind:code={script.content}
lang={script.language}
{initialArgs}
kind={script.kind}
/>
{:else if step === 3}
<CenteredPage>

View File

@@ -23,6 +23,7 @@
export let code: string
export let path: string | undefined
export let lang: Preview.language
export let kind: 'script' | 'trigger' | 'approval' | undefined = undefined
export let initialArgs: Record<string, any> = {}
export let fixedOverflowWidgets = true
@@ -118,6 +119,7 @@
{editor}
{lang}
{websocketAlive}
{kind}
/>
<div class="py-1">
<Button

View File

@@ -0,0 +1,17 @@
<script lang="ts">
import { faBuilding } from '@fortawesome/free-solid-svg-icons'
import { ToggleButton, ToggleButtonGroup } from './common'
import { WindmillIcon } from './icons'
export let selected: 'hub' | 'workspace' = 'hub'
</script>
<div class="max-w-min">
<ToggleButtonGroup bind:selected>
<ToggleButton position="left" value="hub" size="sm">
<WindmillIcon white={selected == 'hub'} height="16px" width="16px" /><div class="mr-1" />Hub
</ToggleButton>
<ToggleButton position="right" value="workspace" size="sm" startIcon={{ icon: faBuilding }}>
Workspace
</ToggleButton>
</ToggleButtonGroup>
</div>

View File

@@ -186,11 +186,23 @@
/>
{:else if editorKind == 'json'}
<div class="border rounded mb-4 w-full border-gray-700">
<SimpleEditor bind:this={editor} autoHeight lang="json" bind:code={variable.value} />
<SimpleEditor
bind:this={editor}
autoHeight
lang="json"
bind:code={variable.value}
fixedOverflowWidgets={false}
/>
</div>
{:else if editorKind == 'yaml'}
<div class="border rounded mb-4 w-full border-gray-700">
<SimpleEditor bind:this={editor} autoHeight lang="yaml" bind:code={variable.value} />
<SimpleEditor
bind:this={editor}
autoHeight
lang="yaml"
bind:code={variable.value}
fixedOverflowWidgets={false}
/>
</div>
{/if}

View File

@@ -9,7 +9,7 @@
import { Preview } from '$lib/gen'
import { DENO_INIT_CODE_CLEAR } from '$lib/script_helpers'
import { classNames, emptySchema } from '$lib/utils'
import { faEdit, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons'
import { faEdit, faPlus, faSave, faTrash } from '@fortawesome/free-solid-svg-icons'
import { getContext } from 'svelte'
import type { AppEditorContext } from '../../types'
import PanelSection from '../settingsPanel/common/PanelSection.svelte'
@@ -115,6 +115,9 @@
fixedOverflowWidgets={false}
/>
{/if}
<div slot="submission" class="flex items-center gap-4">
<Button startIcon={{ icon: faSave }} disabled>Automatically Saved</Button>
</div>
</DrawerContent>
</Drawer>

View File

@@ -8,7 +8,7 @@
export let language: Script.language
const languageLabel = {
[Script.language.PYTHON3]: 'Python 3',
[Script.language.PYTHON3]: 'Python',
[Script.language.DENO]: 'TypeScript',
[Script.language.GO]: 'Go',
[Script.language.BASH]: 'Bash'

View File

@@ -1,48 +1,22 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { faPlus } from '@fortawesome/free-solid-svg-icons'
import Fuse from 'fuse.js'
import { loadHubFlows } from '$lib/utils'
import { Button, ButtonPopup, ButtonPopupItem } from '$lib/components/common'
import ItemPicker from '../ItemPicker.svelte'
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
import { flowStore, initFlow } from '$lib/components/flows/flowStore'
let hubFlows: any[] | undefined = undefined
const drawers: {
hub: ItemPicker | undefined
json: Drawer | undefined
} = {
hub: undefined,
json: undefined
}
let drawer: Drawer | undefined = undefined
let pendingJson: string
let hubFilter = ''
const flowHubFuse: Fuse<any> = new Fuse([], {
includeScore: false,
keys: ['summary']
})
$: filteredHubFlows =
hubFilter.length > 0 ? flowHubFuse.search(hubFilter).map((value) => value.item) : hubFlows ?? []
async function importJson() {
await goto('/flows/add')
Object.assign($flowStore, JSON.parse(pendingJson))
initFlow($flowStore)
drawers.json?.closeDrawer()
}
async function loadHubFlowsWFuse(): Promise<void> {
hubFlows = await loadHubFlows()
flowHubFuse.setCollection(hubFlows ?? [])
drawer?.closeDrawer?.()
}
</script>
@@ -50,30 +24,16 @@
<div class="flex flex-row gap-2">
<ButtonPopup size="sm" spacingSize="xl" startIcon={{ icon: faPlus }} href="/flows/add">
<svelte:fragment slot="main">New Flow</svelte:fragment>
<ButtonPopupItem on:click={() => drawers.json?.toggleDrawer()}>
<ButtonPopupItem on:click={() => drawer?.toggleDrawer?.()}>
Import from raw JSON
</ButtonPopupItem>
</ButtonPopup>
</div>
<!-- WindmillHub script list -->
<ItemPicker
bind:this={drawers.hub}
pickCallback={(path) => {
goto('/flows/add?hub=' + path)
}}
itemName={'Flow'}
extraField="summary"
loadItems={async () => {
await loadHubFlowsWFuse()
return filteredHubFlows.map((x) => ({ ...x, path: x.flow_id }))
}}
/>
<!-- Raw JSON -->
<Drawer bind:this={drawers.json} size="800px">
<DrawerContent title="Import flow from JSON" on:close={() => drawers.json?.toggleDrawer()}>
<SimpleEditor bind:code={pendingJson} lang="json" class="h-full" />
<Drawer bind:this={drawer} size="800px">
<DrawerContent title="Import flow from JSON" on:close={() => drawer?.toggleDrawer?.()}>
<SimpleEditor bind:code={pendingJson} lang="json" class="h-full" fixedOverflowWidgets={false} />
<span slot="submission"><Button size="sm" on:click={importJson}>Import</Button></span>
</DrawerContent>
</Drawer>

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { ToggleButton, ToggleButtonGroup } from '$lib/components/common'
import WindmillIcon from '$lib/components/icons/WindmillIcon.svelte'
import ToggleHubWorkspace from '$lib/components/ToggleHubWorkspace.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import { RawScript, Script } from '$lib/gen'
@@ -24,11 +25,14 @@
<div class="max-w-min">
<ToggleButtonGroup bind:selected={kind}>
<ToggleButton position="left" value="script" size="sm" startIcon={{ icon: faCode }}>
Common
Common &nbsp;<Tooltip>
A common script is simply a script that is neither a trigger nor an approval script.
Those are the majority of the scripts.
</Tooltip>
</ToggleButton>
{#if !shouldDisableTriggerScripts}
<ToggleButton position="center" value="trigger" size="sm" startIcon={{ icon: faBolt }}>
Trigger <Tooltip>
Trigger &nbsp;<Tooltip>
Used as a first step most commonly with a state and a schedule to watch for changes
on an external system, compute the diff since last time, set the new state. The
diffs are then treated one by one with a for-loop.
@@ -36,11 +40,11 @@
</ToggleButton>
{/if}
<ToggleButton position="right" value="approval" size="sm" startIcon={{ icon: faCheck }}>
Approval <Tooltip>
Approval &nbsp;<Tooltip>
An approval step will suspend the execution of a flow until it has been approved
through the resume endpoints or the approval page by and solely by the recipients of
those secret urls. Use getResumeEndpoints from the wmill client to generate those
URLs.
those secret urls. Use `wmill.getResumeUrls()` in Typescript or
`wmill.get_resume_urls()` in Python from the wmill client to generate those URLs.
</Tooltip>
</ToggleButton>
</ToggleButtonGroup>
@@ -48,10 +52,10 @@
</div>
{/if}
<h3 class="pb-2">
Inline new {kind == 'script' ? 'common' : kind} script
Inline new <span class="text-blue-500">{kind == 'script' ? 'common' : kind}</span> script
<Tooltip>
Embed a script directly inside a flow instead of saving the script into your workspace for
reuse. You can always save an inline script later.
reuse. You can always save an inline script to your workspace later.
</Tooltip>
</h3>
<div class="flex flex-row">
@@ -119,24 +123,16 @@
</div>
</div>
<div class="flex flex-row gap-x-2 mt-8 mb-2"
><h3>Use pre-made</h3>
<div class="max-w-min">
<ToggleButtonGroup bind:selected={pick_existing}>
<ToggleButton position="left" value="hub" size="xs">
<WindmillIcon white={pick_existing == 'hub'} height="16px" width="16px" /><div
class="mr-1"
/>Hub
</ToggleButton>
<ToggleButton position="right" value="workspace" size="xs" startIcon={{ icon: faBuilding }}>
Workspace
</ToggleButton>
</ToggleButtonGroup>
</div></div
<h3 class="mb-2 mt-6"
>Use pre-made <span class="text-blue-500">{kind == 'script' ? 'common' : kind}</span> script</h3
>
{#if pick_existing == 'hub'}
<PickHubScript bind:filter {kind} on:pick />
<PickHubScript bind:filter {kind} on:pick
><ToggleHubWorkspace bind:selected={pick_existing} /></PickHubScript
>
{:else}
<WorkspaceScriptPicker bind:filter {kind} on:pick />
<WorkspaceScriptPicker bind:filter {kind} on:pick
><ToggleHubWorkspace bind:selected={pick_existing} /></WorkspaceScriptPicker
>
{/if}
</div>

View File

@@ -224,7 +224,7 @@
<Tab value="advanced-early-stop">Early Stop</Tab>
<Tab value="advanced-suspend">Suspend</Tab>
<Tab value="advanced-sleep">Sleep</Tab>
<Tab value="advanced-same_worker">Same Worker/Shared dir</Tab>
<Tab value="advanced-same_worker">Shared Directory</Tab>
{/if}
</Tabs>
{#if selected === 'advanced-retries'}
@@ -241,15 +241,15 @@
</div>
{:else if selected === 'advanced-same_worker'}
<div class="p-4 h-full overflow-auto">
<Alert type="info" title="Share a directory using same worker">
If same worker is set, all steps will be run on the same worker and will share
the folder `./shared` to pass data between each other.
<Alert type="info" title="Share a directory between steps">
If shared directory is set, will share a folder that will be mounted on
`./shared` for each of them to pass data between each other.
</Alert>
<Button
btnClasses="mt-4"
on:click={() => {
$selectedId = 'settings-same-worker'
}}>Set same worker in the flow settings</Button
}}>Set shared directory in the flow settings</Button
>
</div>
{/if}

View File

@@ -29,7 +29,7 @@
<Tabs selected={defaultTab}>
<Tab value="metadata">Metadata</Tab>
<Tab value="schedule">Schedule</Tab>
<Tab value="same-worker">Same Worker</Tab>
<Tab value="same-worker">Shared Directory</Tab>
<Tab value="graph">Graph</Tab>
<svelte:fragment slot="content">
@@ -84,19 +84,16 @@
</TabContent>
<TabContent value="same-worker" class="p-4 flex flex-col">
<Alert
type="info"
title="Toggle Same Worker to have all steps be ran on the same worker"
>
Steps will be run one after the other on the same worker, and will share a folder at
`./shared` in which they can store heavier data and pass them to the next step. <br
/><br />Beware that the `./shared` folder is not preserved across suspends and sleeps.
<Alert type="info" title="Shared Directory">
Steps will share a folder at `./shared` in which they can store heavier data and pass
them to the next step. <br /><br />Beware that the `./shared` folder is not preserved
across suspends and sleeps.
</Alert>
<span class="my-2 text-sm font-bold">Same Worker</span>
<span class="my-2 text-sm font-bold">Shared Directoryr</span>
<Toggle
bind:checked={$flowStore.value.same_worker}
options={{
right: 'Same Worker'
right: 'Shared Directory on `./shared`'
}}
/>
</TabContent>

View File

@@ -30,7 +30,7 @@
</Badge>
{/if}
{#if $flowStore.value.same_worker}
<Badge color="blue" baseClass="truncate">same worker</Badge>
<Badge color="blue" baseClass="truncate">./shared</Badge>
{/if}
</span>
</span>

View File

@@ -31,14 +31,15 @@
<SearchItems {filter} items={prefilteredItems} bind:filteredItems f={(x) => x.summary} />
<div class="flex flex-col min-h-0">
<div class="w-12/12 pb-2 flex flex-row my-1 gap-1">
<div class="w-full flex mt-1 items-center gap-2 mb-5">
<slot />
<input type="text" placeholder="Search Hub Scripts" bind:value={filter} class="text-2xl grow" />
</div>
<div class="gap-2 w-full flex flex-wrap pb-2">
<div class="gap-2 w-full flex flex-wrap pb-3">
{#each apps as app}
<Badge
class="cursor-pointer hover:bg-gray-200"
class="cursor-pointer hover:bg-gray-200 border"
on:click={() => {
appFilter = appFilter == app ? undefined : app
}}

View File

@@ -1,62 +1,116 @@
<script lang="ts">
import { workspaceStore } from '$lib/stores'
import { createEventDispatcher } from 'svelte'
import Fuse from 'fuse.js'
import { ScriptService } from '$lib/gen'
import SearchItems from '$lib/components/SearchItems.svelte'
import { Badge, Skeleton } from '$lib/components/common'
import { fade } from 'svelte/transition'
import { flip } from 'svelte/animate'
import { emptyString } from '$lib/utils'
export let kind: 'script' | 'trigger' | 'approval' | 'failure' = 'script'
export let isTemplate: boolean | undefined = undefined
let items: Item[] = []
let filteredItems: Item[] | undefined = []
export let filter = ''
const fuseOptions = {
includeScore: false,
keys: ['path', 'summay']
type Item = {
path: string
summary?: string
description?: string
}
const fuse: Fuse<Item> = new Fuse(items, fuseOptions)
let items: Item[] | undefined = undefined
let filteredItems: (Item & { marked?: string })[] | undefined = undefined
export let filter = ''
$: $workspaceStore && kind && loadItems()
$: filteredItems =
filter.length > 0 && items ? fuse.search(filter).map((value) => value.item) : items
async function loadItems(): Promise<void> {
items = await ScriptService.listScripts({ workspace: $workspaceStore!, kind })
fuse.setCollection(items)
items = await ScriptService.listScripts({ workspace: $workspaceStore!, kind, isTemplate })
}
let ownerFilter: string | undefined = undefined
$: prefilteredItems = ownerFilter ? items?.filter((x) => x.path.startsWith(ownerFilter!)) : items
$: owners = Array.from(
new Set(filteredItems?.map((x) => x.path.split('/').slice(0, 2).join('/')) ?? [])
).sort()
const dispatch = createEventDispatcher()
</script>
<SearchItems
{filter}
items={prefilteredItems}
bind:filteredItems
f={(x) => (emptyString(x.summary) ? x.path : x.summary + ' (' + x.path + ')')}
/>
<div class="flex flex-col min-h-0">
<div class="w-12/12 pb-4 mt-1">
<input type="text" placeholder="Search script" bind:value={filter} class="text-2xl grow" />
<div class="w-full flex mt-1 items-center gap-2 mb-3">
<slot />
<input
type="text"
placeholder="Search Workspace Scripts"
bind:value={filter}
class="text-2xl grow"
/>
</div>
{#if filteredItems}
{#if owners.length > 0}
<div class="gap-2 w-full flex flex-wrap my-2">
{#each owners as owner (owner)}
<div in:fade={{ duration: 50 }} animate:flip={{ duration: 100 }}>
<Badge
class="cursor-pointer hover:bg-gray-200"
on:click={() => {
ownerFilter = ownerFilter == owner ? undefined : owner
}}
color={owner === ownerFilter ? 'blue' : 'gray'}
baseClass={owner === ownerFilter ? 'border border-blue-500' : 'border'}
>
{owner}
{#if owner === ownerFilter}&cross;{/if}
</Badge>
</div>
{/each}
</div>
{/if}
{#if filter.length > 0 && filteredItems.length == 0}
<p class="py-5">No items found</p>
{/if}
<ul class="divide-y divide-gray-200 overflow-auto">
{#each filteredItems as obj}
{#each filteredItems as { path, summary, description, marked }}
<li class="flex flex-row w-full">
<button
class="py-4 px-1 gap-1 flex flex-row grow hover:bg-blue-50 bg-white transition-all text-black"
on:click={() => {
dispatch('pick', obj)
dispatch('pick', { path })
}}
>
<div class="flex flex-col">
<div class="text-sm font-semibold flex flex-col">
<span class="mr-2 text-left">{obj['summary'] ?? ''}</span>
<span class="mr-2 text-left">
{#if marked}
{@html marked}
{:else}
{!summary || summary.length == 0 ? path : summary}
{/if}</span
>
<span class="font-normal text-xs text-left italic overflow-hidden"
>{obj['path'] ?? ''}</span
>{path ?? ''}</span
>
</div>
<div class="text-xs font-light italic text-left">{obj['description'] ?? ''}</div>
<div class="text-xs font-light italic text-left">{description ?? ''}</div>
</div>
</button>
</li>
{/each}
</ul>
{:else}
<div class="mt-6" />
{#each new Array(6) as _}
<Skeleton layout={[[4], 0.7]} />
{/each}
{/if}
</div>

View File

@@ -361,7 +361,7 @@
{#if owners.length > 0}
<div class="gap-2 w-full flex flex-wrap my-4">
{#each owners as owner (owner)}
<div in:fade animate:flip={{ duration: 200 }}>
<div in:fade={{ duration: 50 }} animate:flip={{ duration: 100 }}>
<Badge
class="cursor-pointer hover:bg-gray-200"
on:click={() => {

View File

@@ -1,84 +1,30 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { faPlus, faScroll } from '@fortawesome/free-solid-svg-icons'
import Fuse from 'fuse.js'
import { Script } from '$lib/gen'
import { ScriptService } from '$lib/gen'
import { workspaceStore, hubScripts } from '$lib/stores'
import { faPlus } from '@fortawesome/free-solid-svg-icons'
import { ButtonPopup, ButtonPopupItem } from '$lib/components/common'
import type { HubItem } from '../flows/pickers/model'
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
import Icon from 'svelte-awesome'
import WorkspaceScriptPicker from '../flows/pickers/WorkspaceScriptPicker.svelte'
import { goto } from '$app/navigation'
const drawers: {
template: Drawer | undefined
} = {
template: undefined
}
let hubItems: HubItem[]
let templateScripts: Script[] = []
let templateFilter = ''
let filteredTemplates: Script[] | undefined
const fuseOptions = {
includeScore: false,
keys: ['description', 'path', 'content', 'hash', 'summary']
}
const templateFuse: Fuse<Script> = new Fuse(templateScripts, fuseOptions)
$: hubItems = $hubScripts?.filter((x) => x.kind == Script.kind.SCRIPT) || []
$: filteredTemplates =
templateFilter.length > 0
? templateFuse.search(templateFilter).map((value) => value.item)
: templateScripts
async function loadTemplateScripts(): Promise<void> {
templateScripts = await ScriptService.listScripts({
workspace: $workspaceStore!,
isTemplate: true
})
templateFuse.setCollection(templateScripts)
}
let drawer: Drawer | undefined = undefined
</script>
<!-- Buttons -->
<div class="flex flex-row gap-2">
<ButtonPopup size="sm" spacingSize="xl" startIcon={{ icon: faPlus }} href="/scripts/add">
<svelte:fragment slot="main">New Script</svelte:fragment>
<ButtonPopupItem on:click={() => drawers.template?.toggleDrawer()}>
<ButtonPopupItem on:click={() => drawer?.toggleDrawer?.()}>
Import from template
</ButtonPopupItem>
</ButtonPopup>
</div>
<!-- Template script list -->
<Drawer bind:this={drawers.template} size="800px" on:open={loadTemplateScripts}>
<DrawerContent title="Pick a template" on:close={() => drawers.template?.toggleDrawer()}>
<div class="pt-2 pb-4">
<input placeholder="Search templates" bind:value={templateFilter} class="search-bar" />
</div>
<div class="flex flex-col mb-2 md:mb-6">
{#if filteredTemplates && filteredTemplates.length > 0}
{#each filteredTemplates as { summary, path, hash }}
<a
class="p-1 flex flex-row items-baseline gap-2 selected text-gray-700"
href="/scripts/add?template={path}"
>
{#if summary}
<p class="text-sm font-semibold">{summary}</p>
{/if}
<p class="text-sm">{path}</p>
<p class="text-gray-400 text-xs text-right grow">
Last version: {hash}
</p>
</a>
{/each}
{:else}
<p class="text-sm text-gray-700">No templates</p>
{/if}
</div>
<Drawer bind:this={drawer} size="800px">
<DrawerContent title="Pick a template" on:close={() => drawer?.toggleDrawer?.()}>
<WorkspaceScriptPicker
isTemplate
on:pick={(e) => goto(`/scripts/add?template=${e.detail.path}`)}
/>
</DrawerContent>
</Drawer>