Files
windmill/frontend/src/lib/components/EditorBar.svelte
Faton Ramadani 3d162b7bae feat(frontend): Add contextual actions to insert variables or resources (#629)
* feat(frontend): Add actions to Monaco editor

* feat(frontend): Remove useless icon

* feat(front): Move addActions to editorBar
2022-09-27 13:55:46 +02:00

260 lines
6.8 KiB
Svelte

<script lang="ts">
import { ResourceService, ScriptService, VariableService } from '$lib/gen'
import { getScriptByPath, loadHubScripts, sendUserToast } from '$lib/utils'
import { faCode, faCube, faFile, faRotate, faRotateLeft } from '@fortawesome/free-solid-svg-icons'
import Icon from 'svelte-awesome'
import { hubScripts, workspaceStore } from '$lib/stores'
import { Highlight } from 'svelte-highlight'
import { python, typescript } from 'svelte-highlight/languages'
import type Editor from './Editor.svelte'
import ItemPicker from './ItemPicker.svelte'
import Modal from './Modal.svelte'
import ResourceEditor from './ResourceEditor.svelte'
import VariableEditor from './VariableEditor.svelte'
import Button from './common/button/Button.svelte'
export let lang: 'python3' | 'deno' | 'go'
export let editor: Editor
export let websocketAlive: { pyright: boolean; black: boolean; deno: boolean }
let variablePicker: ItemPicker
let resourcePicker: ItemPicker
let scriptPicker: ItemPicker
let variableEditor: VariableEditor
let resourceEditor: ResourceEditor
let codeViewer: Modal
let codeLang: 'python3' | 'deno' | 'go' = 'deno'
let codeContent: string = ''
function addEditorActions() {
editor.addAction('insert-variable', 'Windmill: Insert variable', () => {
variablePicker.openModal()
})
editor.addAction('insert-resource', 'Windmill: Insert resource', () => {
resourcePicker.openModal()
})
}
$: editor && addEditorActions()
async function loadVariables() {
let r: { name: string; path?: string; description?: string }[] = []
const variables = (
await VariableService.listVariable({ workspace: $workspaceStore ?? 'NO_W' })
).map((x) => {
return { name: x.path, ...x }
})
const rvariables = await VariableService.listContextualVariables({
workspace: $workspaceStore ?? 'NO_W'
})
r = r.concat(variables).concat(rvariables)
return r
}
async function loadScripts(): Promise<{ path: string; summary?: string }[]> {
const workspaceScripts: { path: string; summary?: string }[] = await ScriptService.listScripts({
workspace: $workspaceStore ?? 'NO_W'
})
await loadHubScripts()
const hubScripts_ = $hubScripts ?? []
return workspaceScripts.concat(hubScripts_)
}
</script>
<ItemPicker
bind:this={scriptPicker}
pickCallback={async (path, _) => {
const { language, content } = await getScriptByPath(path ?? '')
codeContent = content
codeLang = language
codeViewer.openModal()
}}
closeOnClick={false}
itemName="script"
extraField="summary"
loadItems={loadScripts}
/>
<Modal bind:this={codeViewer}>
<div slot="title">Code</div>
<div slot="content">
{#if codeLang == 'python3'}
<Highlight language={python} code={codeContent} />
{:else if codeLang == 'deno'}
<Highlight language={typescript} code={codeContent} />
{/if}
</div></Modal
>
<ItemPicker
bind:this={variablePicker}
pickCallback={(path, name) => {
if (!path) {
if (lang == 'deno') {
editor.insertAtCursor(`Deno.env.get('${name}')`)
} else if (lang == 'python3') {
if (!editor.getCode().includes('import os')) {
editor.insertAtBeginning('import os\n')
}
editor.insertAtCursor(`os.environ.get("${name}")`)
}
sendUserToast(`${name} inserted at cursor`)
} else {
if (lang == 'deno') {
if (!editor.getCode().includes('import * as wmill from')) {
editor.insertAtBeginning(
`import * as wmill from 'https://deno.land/x/windmill@v${__pkg__.version}/mod.ts'\n`
)
}
editor.insertAtCursor(`(await wmill.getVariable('${path}'))`)
} else if (lang == 'python3') {
if (!editor.getCode().includes('import wmill')) {
editor.insertAtBeginning('import wmill\n')
}
editor.insertAtCursor(`wmill.get_variable("${path}")`)
}
sendUserToast(`${name} inserted at cursor`)
}
}}
itemName="Variable"
extraField="name"
loadItems={loadVariables}
>
<div slot="submission" class="flex flex-row">
<div class="text-xs mr-2 align-middle">
The variable you were looking for does not exist yet?
</div>
<button
class="default-button-secondary"
type="button"
on:click={() => {
variableEditor.initNew()
}}
>
Create a new variable
</button>
</div>
</ItemPicker>
<ItemPicker
bind:this={resourcePicker}
pickCallback={(path, _) => {
if (lang == 'deno') {
if (!editor.getCode().includes('import * as wmill from')) {
editor.insertAtBeginning(
`import * as wmill from 'https://deno.land/x/windmill@v${__pkg__.version}/mod.ts'\n`
)
}
editor.insertAtCursor(`(await wmill.getResource('${path}'))`)
} else if (lang == 'python3') {
if (!editor.getCode().includes('import wmill')) {
editor.insertAtBeginning('import wmill\n')
}
editor.insertAtCursor(`wmill.get_resource("${path}")`)
}
sendUserToast(`${path} inserted at cursor`)
}}
itemName="Resource"
extraField="resource_type"
loadItems={async () =>
await ResourceService.listResource({ workspace: $workspaceStore ?? 'NO_W' })}
>
<div slot="submission" class="flex flex-row">
<div class="text-xs mr-2 align-middle">
The resource you were looking for does not exist yet?
</div>
<button
class="default-button-secondary"
type="button"
on:click={() => {
resourceEditor.initNew()
}}
>
Create a new resource
</button>
</div>
</ItemPicker>
<ResourceEditor bind:this={resourceEditor} on:refresh={resourcePicker.openModal} />
<VariableEditor bind:this={variableEditor} on:create={variablePicker.openModal} />
<div class="flex divide-x items-center">
<div>
<Button
color="light"
btnClasses="mr-1"
on:click={variablePicker.openModal}
size="xs"
spacingSize="md"
startIcon={{ icon: faFile }}
>
Insert variable
</Button>
</div>
<div>
<Button
btnClasses="mx-1"
size="xs"
spacingSize="md"
color="light"
on:click={resourcePicker.openModal}
>
<Icon data={faCube} class="mr-2" />
Insert resource
</Button>
</div>
<div>
<Button
btnClasses="mx-1"
size="xs"
spacingSize="md"
color="light"
on:click={scriptPicker.openModal}
>
<Icon data={faCode} class="mr-2" />
Search script
</Button>
</div>
<div>
<Button
btnClasses="mx-1"
size="xs"
spacingSize="md"
color="light"
on:click={editor.clearContent}
>
<Icon data={faRotateLeft} class="mr-2" />
Reset content
</Button>
</div>
<div>
<Button
btnClasses="ml-1"
size="xs"
spacingSize="md"
color="light"
on:click={editor.reloadWebsocket}
>
<Icon data={faRotate} class="h-4 w-4 mr-2" />
Reload assistants
<span class="ml-1">
{#if lang == 'deno'}
(<span class={websocketAlive.deno ? 'text-green-600' : 'text-red-700'}>Deno</span>)
{:else if lang == 'python3'}
(<span class={websocketAlive.pyright ? 'text-green-600' : 'text-red-700'}>Pyright</span>
&nbsp;
<span class={websocketAlive.black ? 'text-green-600' : 'text-red-700'}>Black</span>)
{/if}
</span>
</Button>
</div>
</div>