App templates (#1448)
* feat(backend): workers are instantly ready and sync with global cache in background * feat(frontend): add templates * feat(frontend): fix * feat(frontend): add psql template * feat(frontend): Move braces --------- Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
This commit is contained in:
@@ -44,7 +44,7 @@
|
||||
async function createInlineScriptByLanguage(
|
||||
language: Preview.language,
|
||||
path: string,
|
||||
subkind: 'pgsql' | 'mysql' | undefined = undefined
|
||||
subkind: 'pgsql' | 'mysql' | 'fetch' | undefined = undefined
|
||||
) {
|
||||
const content =
|
||||
defaultCode(componentType ?? '', subkind || language) ??
|
||||
@@ -139,7 +139,7 @@
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<div class="flex flex-col px-4 py-2 gap-2 text-sm" in:fly={{ duration: 50 }}>
|
||||
<div class="flex flex-col px-4 gap-2 text-sm" in:fly={{ duration: 50 }}>
|
||||
<div class="mt-4 flex justify-between gap-8 mb-2">
|
||||
<div class="font-bold items-baseline">Choose a language:</div>
|
||||
<div class="flex gap-2">
|
||||
@@ -163,61 +163,77 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 flex-row flex-wrap">
|
||||
{#each langs as lang}
|
||||
<FlowScriptPicker
|
||||
label={capitalize(lang)}
|
||||
{lang}
|
||||
on:click={() => {
|
||||
createInlineScriptByLanguage(lang, name)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<FlowScriptPicker
|
||||
label={`PostgreSQL`}
|
||||
lang="pgsql"
|
||||
on:click={() => {
|
||||
createInlineScriptByLanguage(Script.language.DENO, name, 'pgsql')
|
||||
}}
|
||||
/>
|
||||
<div class="flex flex-row w-full justify-between">
|
||||
<div class="">
|
||||
<div class="mb-1 text-sm font-semibold">Backend</div>
|
||||
|
||||
<!-- <FlowScriptPicker
|
||||
label={`MySQL`}
|
||||
lang="mysql"
|
||||
on:click={() => {
|
||||
createInlineScriptByLanguage(Script.language.DENO, name, 'mysql')
|
||||
}}
|
||||
/> -->
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs mb-1 mt-2">
|
||||
Script executed in the client's browser directly:
|
||||
<Tooltip documentationLink="https://docs.windmill.dev/docs/apps/app-runnable#frontend-script">
|
||||
Frontend scripts are executed in the browser and can manipulate the app context directly
|
||||
</Tooltip>
|
||||
<div class="flex gap-2 flex-row flex-wrap">
|
||||
{#each langs as lang}
|
||||
<FlowScriptPicker
|
||||
label={lang === 'deno' ? 'Typescript' : capitalize(lang)}
|
||||
{lang}
|
||||
on:click={() => {
|
||||
createInlineScriptByLanguage(lang, name)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="mt-4 mb-2 text-sm">Typescript templates</div>
|
||||
|
||||
<div class="flex gap-2 flex-row flex-wrap mb-4">
|
||||
<FlowScriptPicker
|
||||
label={`PostgreSQL`}
|
||||
lang="pgsql"
|
||||
on:click={() => {
|
||||
createInlineScriptByLanguage(Script.language.DENO, name, 'pgsql')
|
||||
}}
|
||||
/>
|
||||
|
||||
<FlowScriptPicker
|
||||
label={`HTTP`}
|
||||
lang="fetch"
|
||||
on:click={() => {
|
||||
createInlineScriptByLanguage(Script.language.DENO, name, 'fetch')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<FlowScriptPicker
|
||||
label={`JavaScript`}
|
||||
lang="javascript"
|
||||
on:click={() => {
|
||||
const newInlineScript = {
|
||||
content: `// read outputs and ctx
|
||||
console.log(ctx.email)
|
||||
<div class="">
|
||||
<div class="mb-1 text-sm font-semibold">
|
||||
Frontend
|
||||
<Tooltip
|
||||
documentationLink="https://docs.windmill.dev/docs/apps/app-runnable#frontend-script"
|
||||
>
|
||||
Frontend scripts are executed in the browser and can manipulate the app context directly
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
// access a global state store
|
||||
if (!state.foo) { state.foo = 0 }
|
||||
state.foo += 1
|
||||
|
||||
// you can also navigate (goto), recompute a script (recompute), or set a tab (setTab)
|
||||
|
||||
return state.foo`,
|
||||
language: 'frontend',
|
||||
path: 'frontend script',
|
||||
schema: undefined
|
||||
}
|
||||
dispatch('new', newInlineScript)
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<FlowScriptPicker
|
||||
label={`JavaScript`}
|
||||
lang="javascript"
|
||||
on:click={() => {
|
||||
const newInlineScript = {
|
||||
content: `// read outputs and ctx
|
||||
console.log(ctx.email)
|
||||
|
||||
// access a global state store
|
||||
if (!state.foo) { state.foo = 0 }
|
||||
state.foo += 1
|
||||
|
||||
// you can also navigate (goto), recompute a script (recompute), or set a tab (setTab)
|
||||
|
||||
return state.foo`,
|
||||
language: 'frontend',
|
||||
path: 'frontend script',
|
||||
schema: undefined
|
||||
}
|
||||
dispatch('new', newInlineScript)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -176,7 +176,7 @@
|
||||
<MenuItem on:click={item.onClick} href={item.href}>
|
||||
<div
|
||||
class={classNames(
|
||||
'!text-gray-600 text-left px-4 py-2 gap-2 cursor-pointer hover:bg-gray-100 !text-xs font-normal'
|
||||
'!text-gray-600 text-left px-4 py-2 gap-2 cursor-pointer hover:bg-gray-100 !text-xs font-semibold'
|
||||
)}
|
||||
>
|
||||
{#if item.icon}
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
</MenuButton>
|
||||
<Transition
|
||||
show={open}
|
||||
enter="transition ease-out duration-25"
|
||||
enter="transition ease-out duration-75"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-25"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<span class="text-sm text-blue-600 font-mono">HTTP</span>
|
||||
@@ -5,13 +5,15 @@
|
||||
import type { SvelteComponent } from 'svelte'
|
||||
import { BashIcon, GoIcon, PythonIcon, TypeScriptIcon } from './'
|
||||
import JavaScript from './JavaScript.svelte'
|
||||
import FetchIcon from './FetchIcon.svelte'
|
||||
|
||||
export let lang: SupportedLanguage | 'pgsql' | 'mysql' | 'javascript'
|
||||
export let lang: SupportedLanguage | 'pgsql' | 'mysql' | 'javascript' | 'fetch'
|
||||
export let width = 30
|
||||
export let height = 30
|
||||
export let scale = 1
|
||||
|
||||
const langToComponent: Record<
|
||||
SupportedLanguage | 'pgsql' | 'mysql' | 'javascript',
|
||||
SupportedLanguage | 'pgsql' | 'mysql' | 'javascript' | 'fetch',
|
||||
typeof SvelteComponent
|
||||
> = {
|
||||
go: GoIcon,
|
||||
@@ -20,7 +22,8 @@
|
||||
bash: BashIcon,
|
||||
pgsql: PostgresIcon,
|
||||
mysql: MySQLIcon,
|
||||
javascript: JavaScript
|
||||
javascript: JavaScript,
|
||||
fetch: FetchIcon
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
export let disabled: boolean = false
|
||||
export let label: string
|
||||
export let lang: SupportedLanguage | 'pgsql' | 'mysql' | 'javascript' | undefined = undefined
|
||||
export let lang: SupportedLanguage | 'pgsql' | 'mysql' | 'javascript' | 'fetch' | undefined =
|
||||
undefined
|
||||
export let icon: IconDefinition | undefined = undefined
|
||||
export let iconColor: string | undefined = undefined
|
||||
</script>
|
||||
|
||||
@@ -100,7 +100,25 @@ export async function main(
|
||||
db,
|
||||
)\`INSERT INTO demo VALUES (\${key}, \${value}) RETURNING *\`;
|
||||
return query.rows;
|
||||
}`
|
||||
|
||||
}
|
||||
/**
|
||||
// The following code accepts raw queries. The code above is recommended because it uses SQL prepared statement.
|
||||
import { pgClient, type Resource, type Sql } from "https://deno.land/x/windmill@v1.88.1/mod.ts";
|
||||
|
||||
export async function main(
|
||||
db: Resource<"postgresql">,
|
||||
query: Sql = "SELECT * FROM demo;",
|
||||
) {
|
||||
if(!query) {
|
||||
throw Error('Query must not be empty.')
|
||||
}
|
||||
const { rows } = await pgClient(db).queryObject(query);
|
||||
return rows;
|
||||
}
|
||||
*/
|
||||
|
||||
`
|
||||
|
||||
export const MYSQL_INIT_CODE = `import {
|
||||
mySql,
|
||||
@@ -119,6 +137,37 @@ export async function main(
|
||||
return query.rows;
|
||||
}`
|
||||
|
||||
export const FETCH_INIT_CODE = `export async function main(
|
||||
url: string | undefined,
|
||||
method: string = 'GET',
|
||||
body: Object = {},
|
||||
headers: Record<string, string> = {}
|
||||
): Promise<Response | null> {
|
||||
if (!url) {
|
||||
console.error('Error: URL is undefined')
|
||||
return null
|
||||
}
|
||||
|
||||
const requestOptions: RequestInit = {
|
||||
method: method || 'GET',
|
||||
headers: headers || {}
|
||||
}
|
||||
|
||||
if (requestOptions.method !== 'GET' && requestOptions.method !== 'HEAD' && body !== undefined) {
|
||||
requestOptions.body = JSON.stringify(body)
|
||||
requestOptions.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...requestOptions.headers
|
||||
}
|
||||
}
|
||||
|
||||
return await fetch(url, requestOptions)
|
||||
.then((res) => res.json())
|
||||
.catch(() => {
|
||||
throw new Error('An error occured')
|
||||
})
|
||||
}`
|
||||
|
||||
export const BASH_INIT_CODE = `# arguments of the form X="$I" are parsed as parameters X of type string
|
||||
msg="$1"
|
||||
dflt="\${2:-default value}"
|
||||
@@ -202,7 +251,7 @@ export function isInitialCode(content: string): boolean {
|
||||
export function initialCode(
|
||||
language: 'deno' | 'python3' | 'go' | 'bash',
|
||||
kind: Script.kind,
|
||||
subkind: 'pgsql' | 'mysql' | 'flow' | 'script' | undefined
|
||||
subkind: 'pgsql' | 'mysql' | 'flow' | 'script' | 'fetch' | undefined
|
||||
): string {
|
||||
if (language === 'deno') {
|
||||
if (kind === 'trigger') {
|
||||
@@ -214,6 +263,8 @@ export function initialCode(
|
||||
return POSTGRES_INIT_CODE
|
||||
} else if (subkind === 'mysql') {
|
||||
return MYSQL_INIT_CODE
|
||||
} else if (subkind === 'fetch') {
|
||||
return FETCH_INIT_CODE
|
||||
} else {
|
||||
return DENO_INIT_CODE
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user