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:
Faton Ramadani
2023-04-21 15:09:18 +02:00
committed by GitHub
parent f63c1c5d2e
commit a235dfbe1f
7 changed files with 135 additions and 63 deletions

View File

@@ -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:&nbsp;
<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>

View File

@@ -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}

View File

@@ -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"
>

View File

@@ -0,0 +1 @@
<span class="text-sm text-blue-600 font-mono">HTTP</span>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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
}